using Elwig.Documents;
using Elwig.Helpers;
using Elwig.Helpers.Export;
using Elwig.Helpers.Weighing;
using Elwig.Models.Entities;
using LinqKit;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Threading;
using Xceed.Wpf.Toolkit.Primitives;

namespace Elwig.Windows {
    public partial class DeliveryAdminWindow : AdministrationWindow {

        public readonly bool IsReceipt = false;
        public int? MgNr => Member?.MgNr;

        private bool IsUpdatingGradation = false;
        private Member? Member = null;
        private readonly DispatcherTimer Timer;
        private List<string> TextFilter = [];

        private readonly RoutedCommand CtrlF = new("CtrlF", typeof(DeliveryAdminWindow), [new KeyGesture(Key.F, ModifierKeys.Control)]);
        private readonly RoutedCommand CtrlP = new("CtrlP", typeof(DeliveryAdminWindow), [new KeyGesture(Key.P, ModifierKeys.Control)]);
        private readonly RoutedCommand CtrlShiftP = new("CtrlShiftP", typeof(DeliveryAdminWindow), [new KeyGesture(Key.P, ModifierKeys.Control | ModifierKeys.Shift)]);

        private string? LastScaleError = null;
        private string? ManualWeighingReason = null;
        private string? ScaleId = null;
        private string? WeighingId = null;
        private readonly Button[] WeighingButtons;

        public DeliveryAdminWindow(bool receipt = false) {
            InitializeComponent();
            CommandBindings.Add(new CommandBinding(CtrlF, FocusSearchInput));
            CommandBindings.Add(new CommandBinding(CtrlP, Menu_Print_ShowDeliveryNote_Click));
            CommandBindings.Add(new CommandBinding(CtrlShiftP, Menu_Print_PrintDeliveryNote_Click));
            RequiredInputs = [
                MgNrInput, MemberInput,
                LsNrInput, DateInput, BranchInput,
                SortIdInput, WineVarietyInput,
                GradationOeInput.TextBox, GradationKmwInput.TextBox, WineQualityLevelInput,
                WineOriginInput, WineKgInput,
                WeightInput.TextBox
            ];
            ExemptInputs = [
                SearchInput, SeasonInput, TodayOnlyInput, AllSeasonsInput,
                DeliveryList, DeliveryPartList,
                MemberAddressField,
            ];
            WeighingButtons = [
                WeighingAButton, WeighingBButton, WeighingCButton, WeighingDButton,
            ];
            IsReceipt = receipt;

            Timer = new DispatcherTimer();
            Timer.Tick += new EventHandler(OnSecondPassed);
            Timer.Interval = new TimeSpan(0, 0, 1);

            InitializeDelayTimer(SearchInput, SearchInput_TextChanged);
            SearchInput.TextChanged -= SearchInput_TextChanged;
            SeasonInput.Value = Utils.CurrentLastSeason;

            DoShowWarningWindows = false;

            if (IsReceipt) {
                Title = $"Übernahme - {App.BranchName} - Elwig";
                TodayOnlyInput.IsChecked = true;
                var n = App.CommandScales.Count;
                if (n < 1) WeighingAButton.Visibility = Visibility.Hidden;
                if (n < 2) WeighingBButton.Visibility = Visibility.Hidden;
                if (n < 3) WeighingCButton.Visibility = Visibility.Hidden;
                if (n < 4) WeighingDButton.Visibility = Visibility.Hidden;
                if (n == 1) WeighingAButton.Content = "Wiegen";
                if (n > 1) WeighingAButton.Content = $"Wiegen {App.CommandScales[0].ScaleId}";
                if (n >= 2) WeighingBButton.Content = $"Wiegen {App.CommandScales[1].ScaleId}";
                if (n >= 3) WeighingCButton.Content = $"Wiegen {App.CommandScales[2].ScaleId}";
                if (n >= 4) WeighingDButton.Content = $"Wiegen {App.CommandScales[3].ScaleId}";
                WeighingManualButton.Margin = new Thickness(10, 10 + n * 32, 10, 10);
                foreach (var s in App.EventScales) {
                    s.WeighingEvent += Scale_Weighing;
                }
            } else {
                WeighingManualButton.Visibility = Visibility.Hidden;
                WeighingAButton.Visibility = Visibility.Hidden;
                WeighingBButton.Visibility = Visibility.Hidden;
                WeighingCButton.Visibility = Visibility.Hidden;
                WeighingDButton.Visibility = Visibility.Hidden;
            }
        }

        public DeliveryAdminWindow(int mgnr) : this() {
            Member = Context.Members.Find(mgnr) ?? throw new ArgumentException("MgNr argument has invalid value");
            Title = $"Lieferungen - {Member.AdministrativeName} - Elwig";
            AllSeasonsInput.IsEnabled = true;
        }

        private void Window_Loaded(object sender, RoutedEventArgs evt) {
            OnSecondPassed(null, null);
            Timer.Start();
            LockInputs();
            if (IsReceipt) {
                NewDeliveryButton_Click(null, null);
                if ((Context.Seasons.Find(Utils.CurrentYear)) == null) {
                    MessageBox.Show("Die Saison für das aktuelle Jahr wurde noch nicht erstellt. Neue Lieferungen können nicht abgespeichert werden.",
                        "Saison noch nicht erstellt", MessageBoxButton.OK, MessageBoxImage.Warning);
                }
            }
        }

        private async void Menu_Print_ShowDeliveryNote_Click(object sender, RoutedEventArgs evt) {
            if (DeliveryList.SelectedItem is not Delivery d) return;
            Mouse.OverrideCursor = Cursors.AppStarting;
            try {
                using var doc = new DeliveryNote(d, Context);
                await doc.Generate();
                doc.Show();
            } catch (Exception exc) {
                MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
            }
            Mouse.OverrideCursor = null;
        }

        private async void Menu_Print_PrintDeliveryNote_Click(object sender, RoutedEventArgs evt) {
            if (DeliveryList.SelectedItem is not Delivery d) return;
            Mouse.OverrideCursor = Cursors.AppStarting;
            try {
                using var doc = new DeliveryNote(d, Context);
                await doc.Generate();
                await doc.Print();
            } catch (Exception exc) {
                MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
            }
            Mouse.OverrideCursor = null;
        }

        private async void Menu_Export_Bki_Click(object sender, RoutedEventArgs evt) {
            if (sender is not MenuItem m) return;
            var year = int.Parse(m.Header.ToString()?.Split(" ")[^1] ?? Utils.CurrentLastSeason.ToString());
            var d = new SaveFileDialog() {
                FileName = $"{App.Client.NameToken}-Traubentransportscheinliste-{year}.{Bki.FileExtension}",
                DefaultExt = Bki.FileExtension,
                Filter = "CSV-Datei (*.csv)|*.csv",
                Title = $"Traubentransportscheinliste (BKI) speichern unter - Elwig"
            };
            if (d.ShowDialog() == true) {
                Mouse.OverrideCursor = Cursors.AppStarting;
                using var file = new Bki(d.FileName);
                await file.ExportAsync(year);
                Mouse.OverrideCursor = null;
            }
        }

        private async void Menu_Print_DeliveryJournal_ShowToday_Click(object sender, RoutedEventArgs evt) {
            Mouse.OverrideCursor = Cursors.AppStarting;
            try {
                var doc = new DeliveryJournal(Context, DateOnly.FromDateTime(Utils.Today));
                await doc.Generate();
                doc.Show();
            } catch (Exception exc) {
                MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
            }
            Mouse.OverrideCursor = null;
        }

        private async void Menu_Print_DeliveryJournal_PrintToday_Click(object sender, RoutedEventArgs evt) {
            Mouse.OverrideCursor = Cursors.AppStarting;
            try {
                var doc = new DeliveryJournal(Context, DateOnly.FromDateTime(Utils.Today));
                await doc.Generate();
                await doc.Print();
            } catch (Exception exc) {
                MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
            }
            Mouse.OverrideCursor = null;
        }

        private async void Menu_Print_DeliveryJournal_ShowFilter_Click(object sender, RoutedEventArgs evt) {
            Mouse.OverrideCursor = Cursors.AppStarting;
            try {
                var (f, _, d, _, _) = await GetFilters();
                var doc = new DeliveryJournal(string.Join(" / ", f), d);
                await doc.Generate();
                doc.Show();
            } catch (Exception exc) {
                MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
            }
            Mouse.OverrideCursor = null;
        }

        private async void Menu_Print_DeliveryJournal_PrintFilter_Click(object sender, RoutedEventArgs evt) {
            Mouse.OverrideCursor = Cursors.AppStarting;
            try {
                var (f, _, d, _, _) = await GetFilters();
                var doc = new DeliveryJournal(string.Join(" / ", f), d);
                await doc.Generate();
                doc.Show();
            } catch (Exception exc) {
                MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
            }
            Mouse.OverrideCursor = null;
        }

        private void Menu_Settings_EnableFreeEditing_Checked(object sender, RoutedEventArgs evt) {
            if (IsEditing || IsCreating) {
                DateInput.IsReadOnly = false;
                TimeInput.IsReadOnly = false;
                BranchInput.IsEnabled = true;
                if (IsCreating) TimeInput.Text = "";
                OnSecondPassed(null, null);
            }
        }

        private void Menu_Settings_EnableFreeEditing_Unchecked(object sender, RoutedEventArgs evt) {
            DateInput.IsReadOnly = true;
            TimeInput.IsReadOnly = true;
            BranchInput.IsEnabled = false;
            OnSecondPassed(null, null);
        }

        private void OnSecondPassed(object? sender, EventArgs? evt) {
            if (IsReceipt && IsCreating && !Menu_Settings_EnableFreeEditing.IsChecked) {
                var now = DateTime.Now;
                TimeInput.Text = now.ToString("HH:mm");
                DateInput.Text = now.ToString("dd.MM.yyyy");
                SetDefaultValue(TimeInput);
                SetDefaultValue(DateInput);
            }
        }

        private void InitialDefaultInputs() {
            if (App.Client.HasNetWeighing(BranchInput.SelectedValue as Branch)) {
                GerebeltGewogenInput.IsEnabled = false;
                SetDefaultValue(GerebeltGewogenInput, true);
            } else {
                GerebeltGewogenInput.IsEnabled = true;
                UnsetDefaultValue(GerebeltGewogenInput);
            }

            if (!App.Client.HasNetWeighing(BranchInput.SelectedValue as Branch)) {
                LesewagenInput.IsEnabled = false;
                SetDefaultValue(LesewagenInput, false);
                HandPickedInput.IsThreeState = false;
                UnsetDefaultValue(HandPickedInput);
            } else {
                LesewagenInput.IsEnabled = true;
                UnsetDefaultValue(LesewagenInput);
                HandPickedInput.IsThreeState = true;
                SetDefaultValue(HandPickedInput, null);
            }

            if (App.Client.IsMatzen || App.Client.IsWinzerkeller) {
                GebundenInput.IsEnabled = false;
                SetDefaultValue(GebundenInput, null);
            } else {
                GebundenInput.IsEnabled = true;
                UnsetDefaultValue(GebundenInput);
            }
        }

        private void InitialInputs() {
            LastScaleError = null;
            WeighingId = null;
            ScaleId = null;
            ManualWeighingReason = null;

            ClearOriginalValues();
            ClearDefaultValues();

            GerebeltGewogenInput.IsChecked = App.Client.HasNetWeighing(BranchInput.SelectedValue as Branch);
            LesewagenInput.IsChecked = false;
            HandPickedInput.IsChecked = !App.Client.HasNetWeighing(BranchInput.SelectedValue as Branch) ? true : null;
            GebundenInput.IsChecked = null;
            InitialDefaultInputs();

            WineQualityLevelInput.IsEnabled = false;
            ValidateRequiredInputs();
        }

        private void InitInputs() {
            ControlUtils.SelectComboBoxItem(BranchInput, i => (i as Branch)?.ZwstId, App.ZwstId);
            OnSecondPassed(null, null);
            UpdateLsNr().GetAwaiter().GetResult();
            InitialInputs();
        }

        protected override void UpdateButtons() {
            if (!IsEditing && !IsCreating) return;
            bool ch = HasChanged, v = IsValid;
            ResetButton.IsEnabled = ch;
            SaveButton.IsEnabled = v && ch;
            FinishButton.IsEnabled = v && ch;
            NewDeliveryPartButton.IsEnabled = v && ch;
            CancelCreatingButton.IsEnabled = DeliveryList.SelectedItem == null || DeliveryPartList.SelectedItem == null;
        }

        private void Input_KeyUp(object sender, KeyEventArgs evt) {
            if (sender is not Control ctrl) return;
            if (evt.Key != Key.Enter) return;
            if (ctrl == MgNrInput || ctrl == MemberInput) {
                SortIdInput.Focus();
                SortIdInput.SelectAll();
            } else if (ctrl == SortIdInput || ctrl == WineVarietyInput || ctrl == AttributeInput || ctrl == CultivationInput) {
                GradationOeInput.Focus();
                GradationOeInput.TextBox.SelectAll();
            } else if (ctrl == GradationKmwInput || ctrl == GradationOeInput || ctrl == WineQualityLevelInput) {
                if (WeighingAButton.IsVisible) WeighingAButton.Focus();
                else WeighingManualButton.Focus();
            }
        }

        private async Task RefreshDeliveryList() {
            await RefreshDeliveryListQuery();
        }

        private async Task<(List<string>, IQueryable<Delivery>, IQueryable<DeliveryPart>, Predicate<DeliveryPart>, List<string>)> GetFilters() {
            List<string> filterNames = [];
            IQueryable<Delivery> deliveryQuery = Context.Deliveries;
            if (IsReceipt && App.BranchNum > 1) {
                deliveryQuery = deliveryQuery.Where(d => d.ZwstId == App.ZwstId);
                filterNames.Add($"Zweigstelle {App.BranchName}");
            }
            if (Member != null) {
                deliveryQuery = deliveryQuery.Where(d => d.MgNr == Member.MgNr);
                filterNames.Add(Member.AdministrativeName);
            }
            if (TodayOnlyInput.IsChecked == true) {
                deliveryQuery = deliveryQuery
                    .Where(d => (d.DateString == Utils.Today.ToString("yyyy-MM-dd") && (d.TimeString == null || d.TimeString.CompareTo("03:00:00") > 0)) ||
                                (d.DateString == Utils.Today.AddDays(1).ToString("yyyy-MM-dd") && (d.TimeString == null || d.TimeString.CompareTo("03:00:00") <= 0)));
                filterNames.Add(Utils.Today.ToString("dd.MM.yyyy"));
            } else if (AllSeasonsInput.IsChecked == false) {
                deliveryQuery = deliveryQuery.Where(d => d.Year == SeasonInput.Value);
                filterNames.Add(SeasonInput.Value.ToString() ?? "");
            }

            Expression<Func<DeliveryPart, bool>> prd = p => true;

            var filterVar = new List<string>();
            var filterNotVar = new List<string>();
            var filterQual = new List<string>();
            var filterNotQual = new List<string>();
            var filterMgNr = new List<int>();
            var filterZwst = new List<string>();
            var filterAttr = new List<string>();
            var filterNotAttr = new List<string>();
            var filterCult = new List<string>();
            var filterNotCult = new List<string>();
            var filterDate = new List<(string?, string?)>();
            var filterTime = new List<(string?, string?)>();
            int filterYearGt = 0, filterYearLt = 0;
            double filterKmwGt = 0, filterKmwLt = 0;
            double filterOeGt = 0, filterOeLt = 0;

            var filter = TextFilter.ToList();
            if (filter.Count > 0) {
                var var = await Context.WineVarieties.ToDictionaryAsync(v => v.SortId, v => v);
                var qual = await Context.WineQualityLevels.Where(q => !q.IsPredicate).ToDictionaryAsync(q => q.QualId, q => q);
                var mgnr = await Context.Members.ToDictionaryAsync(m => m.MgNr.ToString(), m => m);
                var zwst = await Context.Branches.ToDictionaryAsync(b => b.Name.ToLower().Split(" ")[0], b => b);
                var attr = await Context.WineAttributes.ToDictionaryAsync(a => a.Name.ToLower().Split(' ')[0], a => a);
                var cult = await Context.WineCultivations.ToDictionaryAsync(c => c.Name.ToLower().Split(' ')[0], c => c);

                for (int i = 0; i < filter.Count; i++) {
                    var e = filter[i];
                    if (e.ToLower() is "r" or "rot") {
                        filterVar.AddRange(var.Values.Where(v => v.IsRed).Select(v => v.SortId));
                        filter.RemoveAt(i--);
                        filterNames.Add("Rotweinsorten");
                    } else if (e.ToLower() is "w" or "weiß" or "weiss") {
                        filterVar.AddRange(var.Values.Where(v => v.IsWhite).Select(v => v.SortId));
                        filter.RemoveAt(i--);
                        filterNames.Add("Weißweinsorten");
                    } else if (e.Length == 2 && var.ContainsKey(e.ToUpper())) {
                        filterVar.Add(e.ToUpper());
                        filter.RemoveAt(i--);
                        filterNames.Add(var[e.ToUpper()].Name);
                    } else if (e.Length == 3 && e[0] == '!' && var.ContainsKey(e[1..].ToUpper())) {
                        filterNotVar.Add(e[1..].ToUpper());
                        filter.RemoveAt(i--);
                        filterNames.Add("außer " + var[e[1..].ToUpper()].Name);
                    } else if (e.Length == 3 && qual.ContainsKey(e.ToUpper())) {
                        var qualId = e.ToUpper();
                        filterQual.Add(qualId);
                        filter.RemoveAt(i--);
                        filterNames.Add(qualId == "WEI" ? "abgewertet" : qual[e.ToUpper()].Name);
                    } else if (e[0] == '!' && qual.ContainsKey(e[1..].ToUpper())) {
                        var qualId = e[1..].ToUpper();
                        filterNotQual.Add(qualId);
                        filter.RemoveAt(i--);
                        filterNames.Add(qualId == "WEI" ? "nicht abgewertet" : "außer " + qual[e[1..].ToUpper()].Name);
                    } else if (e.Length >= 5 && e.Length <= 10 && "abgewertet".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
                        filterQual.Add("WEI");
                        filter.RemoveAt(i--);
                        filterNames.Add("abgewertet");
                    } else if (e.Length >= 6 && e.Length <= 11 && "!abgewertet".StartsWith(e, StringComparison.CurrentCultureIgnoreCase)) {
                        filterNotQual.Add("WEI");
                        filter.RemoveAt(i--);
                        filterNames.Add("nicht abgewertet");
                    } else if (e.All(char.IsAsciiDigit) && mgnr.TryGetValue(e, out var member)) {
                        filterMgNr.Add(int.Parse(e));
                        filter.RemoveAt(i--);
                        filterNames.Add(member.AdministrativeName);
                    } else if (attr.ContainsKey(e.ToLower())) {
                        var a = attr[e.ToLower()];
                        filterAttr.Add(a.AttrId);
                        filter.RemoveAt(i--);
                        filterNames.Add($"Attribut {a.Name}");
                    } else if (e[0] == '!' && attr.ContainsKey(e[1..].ToLower())) {
                        var a = attr[e[1..].ToLower()];
                        filterNotAttr.Add(a.AttrId);
                        filter.RemoveAt(i--);
                        filterNames.Add($"ohne Attribut {a.Name}");
                    } else if (cult.ContainsKey(e.ToLower())) {
                        var c = cult[e.ToLower()];
                        filterCult.Add(c.CultId);
                        filter.RemoveAt(i--);
                        filterNames.Add($"Bewirtschaftung {c.Name}");
                    } else if (e[0] == '!' && cult.ContainsKey(e[1..].ToLower())) {
                        var c = cult[e[1..].ToLower()];
                        filterNotCult.Add(c.CultId);
                        filter.RemoveAt(i--);
                        filterNames.Add($"ohne Bewirtschaftung {c.Name}");
                    } else if (zwst.ContainsKey(e.ToLower())) {
                        var b = zwst[e.ToLower()];
                        filterZwst.Add(b.ZwstId);
                        filter.RemoveAt(i--);
                        filterNames.Add($"Zweigstelle {b.Name}");
                    } else if (e.StartsWith('>') || e.StartsWith('<')) {
                        if (double.TryParse(e[1..], out var num)) {
                            switch ((e[0], num)) {
                                case ('>', <= 30): filterKmwGt = num; break;
                                case ('<', <= 30): filterKmwLt = num; break;
                                case ('>', >= 1900): filterYearGt = (int)num; break;
                                case ('<', >= 1900): filterYearLt = (int)num; break;
                                case ('>', _): filterOeGt = num; break;
                                case ('<', _): filterOeLt = num; break;
                            }
                            filter.RemoveAt(i--);
                        }
                        if (e.Length == 1) filter.RemoveAt(i--);
                    } else if (e.Length > 1 && Utils.FromToRegex.IsMatch(e)) {
                        var parts = e.Split("-");
                        double? from = (parts[0].Length > 0) ? double.Parse(parts[0], CultureInfo.InvariantCulture) : null;
                        double? to = (parts[1].Length > 0) ? double.Parse(parts[1], CultureInfo.InvariantCulture) : null;
                        switch ((from, to)) {
                            case ( <= 30, <= 30):
                            case ( <= 30, null):
                            case (null, <= 30):
                                filterKmwGt = from ?? 0;
                                filterKmwLt = to ?? 0;
                                break;
                            case ( >= 1900, >= 1900):
                            case ( >= 1900, null):
                            case (null, >= 1900):
                                filterYearGt = (int)(from ?? 0);
                                filterYearLt = (int)(to ?? -1) + 1;
                                break;
                            case (_, _):
                                filterOeGt = from ?? 0;
                                filterOeLt = to ?? 0;
                                break;
                        }
                        filter.RemoveAt(i--);
                    } else if (e.Length > 1 && Utils.FromToTimeRegex.IsMatch(e)) {
                        var parts = e.Split("-");
                        filterTime.Add((TimeOnly.TryParse(parts[0], out var from) ? $"{from:HH:mm}" : null, TimeOnly.TryParse(parts[1], out var to) ? $"{to:HH:mm}" : null));
                        filter.RemoveAt(i--);
                        var t = filterTime.Last();
                        if (t.Item1 != null && t.Item2 != null) {
                            filterNames.Add($"{t.Item1}–{t.Item2}");
                        } else if (t.Item1 != null) {
                            filterNames.Add($"ab {t.Item1}");
                        } else if (t.Item2 != null) {
                            filterNames.Add($"bis {t.Item2}");
                        }
                    } else if (DateOnly.TryParse(e, out var date)) {
                        var s = date.ToString("yyyy-MM-dd");
                        filterDate.Add((s, s));
                        filter.RemoveAt(i--);
                        if (filterNames.Contains(SeasonInput.Value.ToString()!) && SeasonInput.Value == date.Year)
                            filterNames.Remove(SeasonInput.Value.ToString()!);
                        filterNames.Add(date.ToString("dd.MM.yyyy"));
                    } else if (Utils.DateFromToRegex.IsMatch(e)) {
                        var parts = e.Split("-");
                        if (parts.Length == 1) {
                            // single date
                            var dParts = parts[0].Split('.');
                            var s = $"{dParts[2]}-{dParts[1].PadLeft(2, '0')}-{dParts[0].PadLeft(2, '0')}";
                            filterDate.Add((s, s));
                            filter.RemoveAt(i--);
                            var n = string.Join('.', s.Split('-').Reverse());
                            if (dParts[2] == "") {
                                filterNames.Remove(SeasonInput.Value.ToString()!);
                                filterNames.Add(n + SeasonInput.Value.ToString());
                            } else {
                                if (SeasonInput.Value.ToString() == dParts[2])
                                    filterNames.Remove(SeasonInput.Value.ToString()!);
                                filterNames.Add(n);
                            }
                        } else if (parts.Length == 2) {
                            // from/to date
                            var d1Parts = parts[0].Split('.');
                            var d2Parts = parts[1].Split('.');
                            var s1 = d1Parts.Length < 2 ? null : $"{d1Parts.ElementAtOrDefault(2)}-{d1Parts[1].PadLeft(2, '0')}-{d1Parts[0].PadLeft(2, '0')}";
                            var s2 = d2Parts.Length < 2 ? null : $"{d2Parts.ElementAtOrDefault(2)}-{d2Parts[1].PadLeft(2, '0')}-{d2Parts[0].PadLeft(2, '0')}";
                            filterDate.Add((s1, s2));
                            filter.RemoveAt(i--);
                            var n1 = s1 == null ? null : string.Join('.', s1.Split('-').Reverse());
                            var n2 = s2 == null ? null : string.Join('.', s2.Split('-').Reverse());
                            if (n1 != null && n2 != null) {
                                filterNames.Add($"{n1}–{n2}");
                            } else if (n1 != null) {
                                filterNames.Add($"ab dem {n1}");
                            } else if (n2 != null) {
                                filterNames.Add($"bis zum {n2}");
                            }
                        }
                    } else if (e.Length > 2 && e.StartsWith('"') && e.EndsWith('"')) {
                        filter[i] = e[1..^1];
                    } else if (e.Length <= 2) {
                        filter.RemoveAt(i--);
                    }
                }

                if (filterYearGt > 0) prd = prd.And(p => p.Year >= filterYearGt);
                if (filterYearLt > 0) prd = prd.And(p => p.Year < filterYearLt);
                if (filterMgNr.Count > 0) prd = prd.And(p => filterMgNr.Contains(p.Delivery.MgNr));
                if (filterDate.Count > 0) {
                    var pr = PredicateBuilder.New<DeliveryPart>(false);
                    foreach (var (d1, d2) in filterDate)
                        pr.Or(p => (d1 == null || d1.CompareTo(p.Delivery.DateString.Substring(10 - d1.Length)) <= 0) && (d2 == null || d2.CompareTo(p.Delivery.DateString.Substring(10 - d2.Length)) >= 0));
                    prd = prd.And(pr);
                }
                if (filterTime.Count > 0) {
                    var pr = PredicateBuilder.New<DeliveryPart>(false);
                    foreach (var (t1, t2) in filterTime)
                        pr.Or(p => (t1 == null || t1.CompareTo(p.Delivery.TimeString) <= 0) && (t2 == null || t2.CompareTo(p.Delivery.TimeString) > 0));
                    prd = prd.And(p => p.Delivery.TimeString != null).And(pr);
                }
                if (filterVar.Count > 0) prd = prd.And(p => filterVar.Contains(p.SortId));
                if (filterNotVar.Count > 0) prd = prd.And(p => !filterNotVar.Contains(p.SortId));
                if (filterQual.Count > 0) prd = prd.And(p => filterQual.Contains(p.QualId));
                if (filterNotQual.Count > 0) prd = prd.And(p => !filterNotQual.Contains(p.QualId));
                if (filterZwst.Count > 0) prd = prd.And(p => filterZwst.Contains(p.Delivery.ZwstId));
                if (filterAttr.Count > 0) prd = prd.And(p => p.AttrId != null && filterAttr.Contains(p.AttrId));
                if (filterNotAttr.Count > 0) prd = prd.And(p => p.AttrId == null || !filterNotAttr.Contains(p.AttrId));
                if (filterCult.Count > 0) prd = prd.And(p => p.CultId != null && filterCult.Contains(p.CultId));
                if (filterNotCult.Count > 0) prd = prd.And(p => p.CultId == null || !filterNotCult.Contains(p.CultId));
                if (filterKmwGt > 0) prd = prd.And(p => p.Kmw >= filterKmwGt);
                if (filterKmwLt > 0) prd = prd.And(p => p.Kmw < filterKmwLt);
                if (filterOeGt > 0) prd = prd.And(p => p.Kmw * (4.54 + 0.022 * p.Kmw) >= filterOeGt);
                if (filterOeLt > 0) prd = prd.And(p => p.Kmw * (4.54 + 0.022 * p.Kmw) < filterOeLt);

                if (filterYearGt > 0 && filterYearLt > 0) {
                    filterNames.Insert(0, $"{filterYearGt}–{filterYearLt - 1}");
                } else if (filterYearGt > 0) {
                    filterNames.Insert(0, $"ab {filterYearGt}");
                } else if (filterYearLt > 0) {
                    filterNames.Insert(0, $"bis {filterYearLt - 1}");
                }
                if (filterKmwGt > 0 && filterKmwLt > 0) {
                    filterNames.Add($"{filterKmwGt:N1}–{filterKmwLt:N1} °KMW");
                } else if (filterKmwGt > 0) {
                    filterNames.Add($"ab {filterKmwGt:N1} °KMW");
                } else if (filterKmwLt > 0) {
                    filterNames.Add($"unter {filterKmwLt:N1} °KMW");
                }
                if (filterOeGt > 0 && filterOeLt > 0) {
                    filterNames.Add($"{filterOeGt:N1}–{filterOeLt:N1} °Oe");
                } else if (filterOeGt > 0) {
                    filterNames.Add($"ab {filterOeGt:N1} °Oe");
                } else if (filterOeLt > 0) {
                    filterNames.Add($"unter {filterOeLt:N1} °Oe");
                }
            }

            IQueryable<DeliveryPart> dpq = deliveryQuery
                .SelectMany(d => d.Parts)
                .Where(prd)
                .OrderBy(p => p.Delivery.DateString)
                .ThenBy(p => p.Delivery.TimeString)
                .ThenBy(p => p.Delivery.LsNr)
                .ThenBy(p => p.DPNr);

            return (filterNames, dpq.Select(p => p.Delivery).Distinct().OrderBy(d => d.DateString).ThenBy(d => d.TimeString), dpq, prd.Invoke, filter);
        }

        private static void AddToolTipCell(Grid grid, string text, int row, int col, int colSpan = 1, bool bold = false, bool alignRight = false, bool alignCenter = false) {
            var tb = new TextBlock() {
                Text = text,
                TextAlignment = alignRight ? TextAlignment.Right : alignCenter ? TextAlignment.Center : TextAlignment.Left,
                Margin = new(0, 12 * row, 0, 0),
                FontWeight = bold ? FontWeights.Bold : FontWeights.Normal,
            };
            tb.SetValue(Grid.ColumnProperty, col);
            tb.SetValue(Grid.ColumnSpanProperty, colSpan);
            grid.Children.Add(tb);
        }

        private void AddWeightToolTipRow(int row, string? h1, string? h2, int weight, int? total1, int total2) {
            var bold = h2 == null;
            if (h1 != null) AddToolTipCell(StatusWeightToolTip, h1 + ":", row, 0, (h2 == null) ? 2 : 1, bold);
            if (h2 != null) AddToolTipCell(StatusWeightToolTip, h2 + ":", row, 1, 1, bold);
            AddToolTipCell(StatusWeightToolTip, $"{weight:N0} kg", row, 2, 1, bold, true);
            if (total1 != null && total1 != 0)
                AddToolTipCell(StatusWeightToolTip, $"{weight * 100.0 / total1:N1} %", row, 3, 1, bold, true);
            if (total2 != 0)
                AddToolTipCell(StatusWeightToolTip, $"{weight * 100.0 / total2:N1} %", row, 4, 1, bold, true);
        }

        private void AddGradationToolTipRow(int row, string? h1, string? h2, double min, double avg, double max) {
            var bold = h2 == null;
            if (h1 != null) AddToolTipCell(StatusGradationToolTip, h1 + ":", row, 0, (h2 == null) ? 2 : 1, bold);
            if (h2 != null) AddToolTipCell(StatusGradationToolTip, h2 + ":", row, 1, 1, bold);
            AddToolTipCell(StatusGradationToolTip, $"{min:N1}°", row, 2, 1, bold, true);
            AddToolTipCell(StatusGradationToolTip, $"{avg:N1}°", row, 3, 1, bold, true);
            AddToolTipCell(StatusGradationToolTip, $"{max:N1}°", row, 4, 1, bold, true);
        }

        private async Task RefreshDeliveryListQuery(bool updateSort = false) {
            var (_, deliveryQuery, deliveryPartsQuery, predicate, filter) = await GetFilters();
            var deliveries = await deliveryQuery.ToListAsync();
            deliveries.Reverse();

            if (filter.Count > 0 && deliveries.Count > 0) {
                var dict = deliveries.AsParallel()
                    .ToDictionary(d => d, d => d.SearchScore(TextFilter))
                    .OrderByDescending(a => a.Value)
                    .ThenBy(a => a.Key.DateTime);
                var threshold = dict.Select(a => a.Value).Max() * 3 / 4;
                deliveries = dict
                    .Where(a => a.Value > threshold)
                    .Select(a => a.Key)
                    .ToList();
            }

            deliveries.ForEach(d => { d.PartFilter = predicate; });
            ControlUtils.RenewItemsSource(DeliveryList, deliveries, d => ((d as Delivery)?.Year, (d as Delivery)?.DId),
                DeliveryList_SelectionChanged, filter.Count > 0 ? ControlUtils.RenewSourceDefault.IfOnly : ControlUtils.RenewSourceDefault.None, !updateSort);
            await RefreshDeliveryParts();

            var members = deliveries.Select(d => d.Member).DistinctBy(m => m.MgNr).ToList();
            StatusMembers.Text = $"Mitglieder: {members.Count}" + (members.Count > 0 && members.Count <= 4 ? $" ({string.Join(", ", members.Select(m => m.AdministrativeName))})" : "");
            StatusMembers.ToolTip = StatusMembers.Text;
            StatusDeliveries.Text = $"Lieferungen: {deliveries.Count}";

            if (filter.Count == 0) {
                var deliveryParts = deliveryPartsQuery;
                var n = await deliveryParts.CountAsync();
                StatusDeliveries.Text = $"Lieferungen: {deliveries.Count} ({n})";
                var varieties = await deliveryParts.Select(d => d.SortId).Distinct().ToListAsync();
                StatusVarieties.Text = $"Sorten: {varieties.Count}" + (varieties.Count > 0 && varieties.Count <= 10 ? $" ({string.Join(", ", varieties)})" : "");

                StatusWeightToolTip.Children.Clear();
                StatusGradationToolTip.Children.Clear();

                var weight = await deliveryParts.SumAsync(p => p.Weight);
                StatusWeight.Text = $"Gewicht: {weight:N0} kg";
                AddWeightToolTipRow(0, "Gewicht", null, weight, null, weight);

                if (n > 0) {
                    var kmwMin = await deliveryParts.MinAsync(p => p.Kmw);
                    var kmwAvg = Utils.AggregateDeliveryPartsKmw(deliveryParts);
                    var kmwMax = await deliveryParts.MaxAsync(p => p.Kmw);
                    StatusGradation.Text = $"Gradation: {kmwMin:N1}° / {kmwAvg:N1}° / {kmwMax:N1}°";
                    AddToolTipCell(StatusGradationToolTip, "Min.", 0, 2, 1, false, false, true);
                    AddToolTipCell(StatusGradationToolTip, "⌀", 0, 3, 1, false, false, true);
                    AddToolTipCell(StatusGradationToolTip, "Max.", 0, 4, 1, false, false, true);
                    AddGradationToolTipRow(1, "Gradation", null, kmwMin, kmwAvg, kmwMax);

                    var attrGroups = await deliveryParts
                        .GroupBy(p => new { Attr = p.Attribute.Name, Cult = p.Cultivation.Name })
                        .Select(g => new {
                            g.Key.Attr,
                            g.Key.Cult,
                            Weight = g.Sum(p => p.Weight),
                            Min = g.Min(p => p.Kmw),
                            Avg = g.Sum(p => p.Kmw * p.Weight) / g.Sum(p => p.Weight),
                            Max = g.Max(p => p.Kmw),
                        })
                        .OrderByDescending(g => g.Weight)
                        .ThenBy(g => g.Attr)
                        .ToListAsync();
                    var sortGroups = await deliveryParts
                        .GroupBy(p => p.SortId)
                        .Select(g => new {
                            SortId = g.Key,
                            Weight = g.Sum(p => p.Weight),
                            Min = g.Min(p => p.Kmw),
                            Avg = g.Sum(p => p.Kmw * p.Weight) / g.Sum(p => p.Weight),
                            Max = g.Max(p => p.Kmw),
                        })
                        .OrderByDescending(g => g.Weight)
                        .ThenBy(g => g.SortId)
                        .ToListAsync();
                    var groups = await deliveryParts
                        .GroupBy(p => new {
                            Attr = p.Attribute.Name,
                            Cult = p.Cultivation.Name,
                            p.SortId,
                        })
                        .Select(g => new {
                            g.Key.Attr,
                            g.Key.Cult,
                            g.Key.SortId,
                            Weight = g.Sum(p => p.Weight),
                            Min = g.Min(p => p.Kmw),
                            Avg = g.Sum(p => p.Kmw * p.Weight) / g.Sum(p => p.Weight),
                            Max = g.Max(p => p.Kmw)
                        })
                        .OrderByDescending(g => g.SortId)
                        .ThenBy(g => g.Attr)
                        .ThenBy(g => g.SortId)
                        .ToListAsync();

                    int rowNum = 1;
                    foreach (var attrG in attrGroups) {
                        rowNum++;
                        var name = attrG.Attr == null && attrG.Cult == null ? null : attrG.Attr + (attrG.Attr != null && attrG.Cult != null ? " / " : "") + attrG.Cult;
                        AddWeightToolTipRow(rowNum++, name, null, attrG.Weight, attrG.Weight, weight);
                        foreach (var g in groups.Where(g => g.Attr == attrG.Attr && g.Cult == attrG.Cult).OrderByDescending(g => g.Weight).ThenBy(g => g.SortId)) {
                            AddWeightToolTipRow(rowNum++, null, g.SortId, g.Weight, attrG.Weight, weight);
                        }
                    }
                    rowNum = 2;
                    foreach (var attrG in attrGroups) {
                        rowNum++;
                        var name = attrG.Attr == null && attrG.Cult == null ? null : attrG.Attr + (attrG.Attr != null && attrG.Cult != null ? " / " : "") + attrG.Cult;
                        AddGradationToolTipRow(rowNum++, name, null, attrG.Min, attrG.Avg, attrG.Max);
                        foreach (var g in groups.Where(g => g.Attr == attrG.Attr && g.Cult == attrG.Cult).OrderByDescending(g => g.Avg).ThenBy(g => g.SortId)) {
                            AddGradationToolTipRow(rowNum++, null, g.SortId, g.Min, g.Avg, g.Max);
                        }
                    }

                    if (attrGroups.Count == 1) {
                        var g = attrGroups.First();
                        var name = g.Attr == null && g.Cult == null ? null : g.Attr + (g.Attr != null && g.Cult != null ? " / " : "") + g.Cult;
                        if (name != null) {
                            StatusWeight.Text += $" [{name}]";
                            StatusGradation.Text += $" [{name}]";
                        }
                        if (sortGroups.Count > 1 && sortGroups.Count <= 4) {
                            StatusWeight.Text += $" = {string.Join(" + ", sortGroups.Select(g => $"{g.Weight:N0} kg ({(double)g.Weight / weight:0%})" + (g.SortId == null ? "" : $" [{g.SortId}]")))}";
                            StatusGradation.Text += $" = {string.Join(" + ", sortGroups.Select(g => $"{g.Min:N1}/{g.Avg:N1}/{g.Max:N1}" + (g.SortId == null ? "" : $" [{g.SortId}]")))}";

                        }
                    } else if (attrGroups.Count <= 4) {
                        StatusWeight.Text += $" = {string.Join(" + ", attrGroups.Select(g => $"{g.Weight:N0} kg ({(double)g.Weight / weight:0%})" + (g.Attr == null && g.Cult == null ? "" : $" [{g.Attr}{(g.Attr != null && g.Cult != null ? " / " : "")}{g.Cult}]")))}";
                        StatusGradation.Text += $" = {string.Join(" + ", attrGroups.Select(g => $"{g.Min:N1}/{g.Avg:N1}/{g.Max:N1}" + (g.Attr == null && g.Cult == null ? "" : $" [{g.Attr}{(g.Attr != null && g.Cult != null ? " / " : "")}{g.Cult}]")))}";
                    }
                } else {
                    StatusGradation.Text = "Gradation: -";
                }
            } else {
                StatusVarieties.Text = "Sorten: -";
                StatusWeight.Text = "Gewicht: -";
                StatusGradation.Text = "Gradation: -";
            }

            StatusVarieties.ToolTip = StatusVarieties.Text;
        }

        protected override async Task OnRenewContext() {
            await base.OnRenewContext();

            if (Member != null) {
                if (Context.Members.Find(Member.MgNr) is not Member m) {
                    Close();
                    return;
                }
                Member = m;
                Title = $"Lieferungen - {Member.AdministrativeName} - Elwig";
            }

            Menu_Export_Bki.Items.Clear();
            foreach (var s in await Context.Seasons.OrderByDescending(s => s.Year).ToListAsync()) {
                var i = new MenuItem {
                    Header = $"Saison {s.Year}",
                };
                i.Click += Menu_Export_Bki_Click;
                Menu_Export_Bki.Items.Add(i);
            }

            await RefreshDeliveryList();
            var d = DeliveryList.SelectedItem as Delivery;
            var y = d?.Year ?? Utils.CurrentLastSeason;
            ControlUtils.RenewItemsSource(MemberInput, await Context.Members.Where(m => m.IsActive || !IsCreating).OrderBy(m => m.FamilyName).ThenBy(m => m.GivenName).ToListAsync(), i => (i as Member)?.MgNr);
            ControlUtils.RenewItemsSource(BranchInput, await Context.Branches.OrderBy(b => b.Name).ToListAsync(), i => (i as Branch)?.ZwstId);
            ControlUtils.RenewItemsSource(WineVarietyInput, await Context.WineVarieties.OrderBy(v => v.Name).ToListAsync(), i => (i as WineVar)?.SortId);
            var attrList = await Context.WineAttributes.Where(a => !IsCreating || a.IsActive).OrderBy(a => a.Name).Cast<object>().ToListAsync();
            attrList.Insert(0, new NullItem(""));
            ControlUtils.RenewItemsSource(AttributeInput, attrList, i => (i as WineAttr)?.AttrId, null, ControlUtils.RenewSourceDefault.First);
            var cultList = await Context.WineCultivations.OrderBy(a => a.Name).Cast<object>().ToListAsync();
            cultList.Insert(0, new NullItem(""));
            ControlUtils.RenewItemsSource(CultivationInput, cultList, i => (i as WineCult)?.CultId, null, ControlUtils.RenewSourceDefault.First);
            ControlUtils.RenewItemsSource(WineQualityLevelInput, await Context.WineQualityLevels.ToListAsync(), i => (i as WineQualLevel)?.QualId);
            ControlUtils.RenewItemsSource(ModifiersInput, await Context.Modifiers.Where(m => m.Year == y).OrderBy(m => m.Ordering).ToListAsync(), i => (i as Modifier)?.ModId);
            ControlUtils.RenewItemsSource(WineOriginInput, (await Context.WineOrigins.ToListAsync()).OrderByDescending(o => o.SortKey).ThenBy(o => o.HkId), i => (i as WineOrigin)?.HkId);
            var kgList = await Context.WbKgs.Select(k => k.AtKg).OrderBy(k => k.Name).Cast<object>().ToListAsync();
            kgList.Insert(0, new NullItem());
            ControlUtils.RenewItemsSource(WineKgInput, kgList, i => (i as AT_Kg)?.KgNr);
            UpdateRdInput();
            if (IsCreating) await UpdateLsNr();

            await RefreshDeliveryParts();
            RefreshInputs();
        }

        private void FocusSearchInput(object sender, RoutedEventArgs evt) {
            if (!IsEditing && !IsCreating) {
                SearchInput.Focus();
                SearchInput.SelectAll();
            }
        }

        private async Task RefreshDeliveryParts() {
            if (DeliveryList.SelectedItem is Delivery d) {
                ControlUtils.RenewItemsSource(ModifiersInput, await Context.Modifiers.Where(m => m.Year == d.Year).OrderBy(m => m.Ordering).ToListAsync(), i => (i as Modifier)?.ModId);
                ControlUtils.RenewItemsSource(DeliveryPartList, d.FilteredParts.OrderBy(p => p.DPNr).ToList(), i => ((i as DeliveryPart)?.Year, (i as DeliveryPart)?.DId, (i as DeliveryPart)?.DPNr), DeliveryPartList_SelectionChanged, ControlUtils.RenewSourceDefault.First);
            } else {
                ControlUtils.RenewItemsSource(ModifiersInput, await Context.Modifiers.Where(m => m.Year == Utils.CurrentLastSeason).OrderBy(m => m.Ordering).ToListAsync(), i => (i as Modifier)?.ModId);
                DeliveryPartList.ItemsSource = null;
            }
        }

        private void RefreshInputs(bool validate = false) {
            ClearInputStates();
            if (DeliveryPartList.SelectedItem is DeliveryPart p) {
                FillInputs(p);
            } else if (DeliveryList.SelectedItem is Delivery d) {
                FillInputs(d);
            } else {
                ClearOriginalValues();
                ClearDefaultValues();
                ClearInputs(validate);
                ClearInputStates();
            }            
            GC.Collect();
        }

        private void FillInputs(Delivery d) {
            ClearOriginalValues();
            ClearDefaultValues();

            MgNrInput.Text = d.MgNr.ToString();
            ControlUtils.SelectComboBoxItem(BranchInput, i => (i as Branch)?.ZwstId, d.ZwstId);
            LsNrInput.Text = d.LsNr;
            DateInput.Text = d.Date.ToString("dd.MM.yyyy");
            TimeInput.Text = d.Time?.ToString("HH:mm") ?? "";
            CommentInput.Text = d.Comment ?? "";

            SortIdInput.Text = "";
            GradationKmwInput.Text = "";
            WeightInput.Text = "";
            ManualWeighingInput.IsChecked = false;
            PartCommentInput.Text = "";
            TemperatureInput.Text = "";
            AcidInput.Text = "";

            FinishInputFilling();
        }

        private void FillInputs(DeliveryPart p) {
            FillInputs(p.Delivery);
            ClearOriginalValues();
            ClearDefaultValues();

            SortIdInput.Text = p?.SortId ?? "";
            ControlUtils.SelectComboBoxItem(AttributeInput, p?.Attribute, i => (i as WineAttr)?.AttrId);
            ControlUtils.SelectComboBoxItem(CultivationInput, p?.Cultivation, i => (i as WineCult)?.CultId);
            GradationKmwInput.Text = (p != null) ? $"{p.Kmw:N1}" : "";
            ControlUtils.SelectComboBoxItem(WineQualityLevelInput, q => (q as WineQualLevel)?.QualId, p?.QualId);
            ControlUtils.SelectComboBoxItem(WineKgInput, k => (k as AT_Kg)?.KgNr, p?.KgNr);
            ControlUtils.SelectComboBoxItem(WineRdInput, r => (r as WbRd)?.RdNr, p?.RdNr);
            ControlUtils.SelectComboBoxItem(WineOriginInput, r => (r as WineOrigin)?.HkId, p?.HkId);
            WeightInput.Text = (p != null) ? $"{p.Weight:N0}" : "";
            ManualWeighingInput.IsChecked = p?.IsManualWeighing ?? false;
            GerebeltGewogenInput.IsChecked = p?.IsNetWeight ?? false;
            ControlUtils.SelectCheckComboBoxItems(ModifiersInput, p?.Modifiers, i => (i as Modifier)?.ModId);
            PartCommentInput.Text = p?.Comment ?? "";
            TemperatureInput.Text = (p != null && p.Temperature != null) ? $"{p.Temperature:N1}" : "";
            AcidInput.Text = (p != null && p.Acid != null) ? $"{p.Acid:N1}" : "";
            LesewagenInput.IsChecked = p?.IsLesewagen ?? false;
            HandPickedInput.IsChecked = p?.IsHandPicked;
            GebundenInput.IsChecked = p?.IsGebunden;

            ScaleId = p?.ScaleId;
            WeighingId = p?.WeighingId;
            ManualWeighingReason = p?.WeighingReason;

            FinishInputFilling();
        }

        private async Task<DeliveryPart> UpdateDeliveryPart(Delivery? d, DeliveryPart? p) {
            int year, did, dpnr;
            bool deliveryNew = (d == null), partNew = (p == null);
            var originalMgNr = d?.MgNr;
            var originalMemberKgNr = d?.Member?.DefaultKgNr;
            if (d == null) {
                d = Context.CreateProxy<Delivery>();
                year = Utils.CurrentYear;
                did = await Context.NextDId(year);
            } else {
                year = d.Year;
                did = d.DId;
            }
            if (p == null) {
                p = Context.CreateProxy<DeliveryPart>();
                dpnr = await Context.NextDPNr(year, did);
            } else {
                dpnr = p.DPNr;
            }
            d.Year = year;
            d.DId = did;
            p.Year = year;
            p.DId = did;
            p.DPNr = dpnr;

            d.DateString = string.Join("-", DateInput.Text.Split(".").Reverse());
            if (deliveryNew || InputHasChanged(DateInput)) {
                d.LNr = await Context.NextLNr(d.Date);
            }
            if (IsCreating && !InputIsNotDefault(TimeInput)) {
                d.TimeString = DateTime.Now.ToString("HH:mm:ss");
            } else if (IsCreating || InputHasChanged(TimeInput)) {
                d.TimeString = (TimeInput.Text != "") ? TimeInput.Text + ":00" : null;
            }
            d.ZwstId = (BranchInput.SelectedItem as Branch)!.ZwstId;
            d.LsNr = LsNrInput.Text;
            d.MgNr = int.Parse(MgNrInput.Text);
            d.Comment = (CommentInput.Text == "") ? null : CommentInput.Text;

            p.SortId = (WineVarietyInput.SelectedItem as WineVar)!.SortId;
            p.AttrId = (AttributeInput.SelectedItem as WineAttr)?.AttrId;
            p.CultId = (CultivationInput.SelectedItem as WineCult)?.CultId;
            p.Kmw = double.Parse(GradationKmwInput.Text);
            p.QualId = (WineQualityLevelInput.SelectedItem as WineQualLevel)!.QualId;
            p.HkId = (WineOriginInput.SelectedItem as WineOrigin)!.HkId;
            p.KgNr = (WineKgInput.SelectedItem as AT_Kg)?.KgNr;
            p.RdNr = (WineRdInput.SelectedItem as WbRd)?.RdNr;

            p.IsNetWeight = GerebeltGewogenInput.IsChecked ?? false;
            p.IsHandPicked = HandPickedInput.IsChecked;
            p.IsLesewagen = LesewagenInput.IsChecked;
            p.IsGebunden = GebundenInput.IsChecked;
            p.Temperature = (TemperatureInput.Text == "") ? null : double.Parse(TemperatureInput.Text);
            p.Acid = (AcidInput.Text == "") ? null : double.Parse(AcidInput.Text);
            p.Comment = (PartCommentInput.Text == "") ? null : PartCommentInput.Text;

            p.Weight = int.Parse(WeightInput.Text.Replace(Utils.GroupSeparator, ""));
            p.IsManualWeighing = ManualWeighingInput.IsChecked ?? false;
            p.ScaleId = ScaleId;
            p.WeighingId = WeighingId;
            p.WeighingReason = ManualWeighingReason;

            EntityEntry<Delivery>? dEntry = null;
            EntityEntry<DeliveryPart>? pEntry = null;
            try {
                if (IsEditing) {
                    dEntry = Context.Update(d);
                    pEntry = Context.Update(p);
                } else if (IsCreating) {
                    dEntry = deliveryNew ? await Context.AddAsync(d) : Context.Update(d);
                    pEntry = partNew ? await Context.AddAsync(p) : Context.Update(p);
                }

                await Context.UpdateDeliveryPartModifiers(p, ModifiersInput.SelectedItems.Cast<Modifier>());

                if (originalMgNr != null && originalMgNr.Value != d.MgNr) {
                    // update origin (KgNr), if default is selected
                    var newKgNr = (await Context.Members.FindAsync(d.MgNr))?.DefaultKgNr;
                    foreach (var part in d.Parts.Where(part => part.DPNr != dpnr && part.KgNr == originalMemberKgNr)) {
                        part.KgNr = newKgNr;
                        Context.Update(part);
                    }
                }

                await Context.SaveChangesAsync();
            } catch (Exception exc) {
                if (dEntry != null) {
                    dEntry.State = EntityState.Detached;
                    await dEntry.ReloadAsync();
                }
                if (pEntry != null) {
                    pEntry.State = EntityState.Detached;
                    await pEntry.ReloadAsync();
                }
                var str = "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\n" + exc.Message;
                if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
                MessageBox.Show(str, "Lieferung aktualisieren", MessageBoxButton.OK, MessageBoxImage.Error);
            }

            return p;
        }

        private void WeighingButton_Click(object sender, RoutedEventArgs evt) {
            int index = Array.IndexOf(WeighingButtons, sender as Button);
            if (index >= 0) WeighingButton_Click(index);
        }

        private async void WeighingButton_Click(int index) {
            DisableWeighingButtons();
            var start = DateTimeOffset.Now.ToUnixTimeMilliseconds();
            FinishButton.IsEnabled = false;
            NewDeliveryPartButton.IsEnabled = false;
            CancelCreatingButton.IsEnabled = false;
            var s = App.CommandScales[index];
            try {
                var res = await s.Weigh();
                OnWeighingResult(s, res);
            } catch (Exception ex) {
                LastScaleError = ex.Message.Split(": ")[^1];
                OnWeighingResult(s, new() { Weight = 0 });
                MessageBox.Show($"Beim Wiegen ist ein Fehler aufgetreten:\n\n{ex.Message}", "Waagenfehler",
                       MessageBoxButton.OK, MessageBoxImage.Error);
            }
            ManualWeighingReason = null;
            ManualWeighingInput.IsChecked = false;
            var end = DateTimeOffset.Now.ToUnixTimeMilliseconds();
            int diff = (int)(end - start);
            if (diff < 1000 && WeightInput.Text.Length != 0) await Task.Delay(1000 - diff);
            EnableWeighingButtons();
        }

        private void OnWeighingResult(IScale scale, WeighingResult res) {
            if ((res.Weight ?? 0) > 0 && res.FullWeighingId != null) {
                WeightInput.Text = $"{res.Weight:N0}";
                ScaleId = scale.ScaleId;
                WeighingId = res.FullWeighingId;
                ManualWeighingReason = null;
                ManualWeighingInput.IsChecked = false;
            } else {
                WeightInput.Text = "";
                ScaleId = null;
                WeighingId = null;
            }
            LastScaleError = null;
            TextBox_TextChanged(WeightInput, null);
            UpdateButtons();
        }

        private void Scale_Weighing(object sender, WeighingEventArgs evt) {
            if (sender is not IScale scale) return;
            App.MainDispatcher.BeginInvoke(() => OnWeighingResult(scale, evt.Result));
        }

        private async void SearchInput_TextChanged(object sender, RoutedEventArgs evt) {
            TextFilter = SearchInput.Text.ToLower().Split(" ").ToList().FindAll(e => e.Length > 0);
            await RefreshDeliveryListQuery(true);
        }

        private async void SeasonInput_ValueChanged(object sender, RoutedEventArgs evt) {
            if (SeasonInput.Value == null) return;
            TodayOnlyInput.IsChecked = false;
            AllSeasonsInput.IsChecked = false;
            await RefreshDeliveryListQuery();
        }

        private async void TodayOnlyInput_Changed(object sender, RoutedEventArgs evt) {
            if (TodayOnlyInput.IsChecked == true && AllSeasonsInput.IsChecked == false) {
                SeasonInput.Value = Utils.Today.Year;
                TodayOnlyInput.IsChecked = true;
            }
            await RefreshDeliveryListQuery();
        }

        private async void AllSeasonsInput_Changed(object sender, RoutedEventArgs evt) {
            if (AllSeasonsInput.IsChecked == true) {
                SeasonInput.IsEnabled = false;
                SeasonInput.Text = "";
            } else {
                SeasonInput.IsEnabled = true;
                var today = TodayOnlyInput.IsChecked;
                SeasonInput.Value = Utils.CurrentLastSeason;
                TodayOnlyInput.IsChecked = today;
            }
            await RefreshDeliveryListQuery();
        }

        private async void DeliveryList_SelectionChanged(object sender, SelectionChangedEventArgs evt) {
            await RefreshDeliveryParts();
            if (DeliveryList.SelectedItem != null) {
                DeleteDeliveryButton.IsEnabled = true;
                Menu_Print_ShowDeliveryNote.IsEnabled = !IsEditing && !IsCreating;
                Menu_Print_PrintDeliveryNote.IsEnabled = !IsEditing && !IsCreating;
            } else {
                DeleteDeliveryButton.IsEnabled = false;
                Menu_Print_ShowDeliveryNote.IsEnabled = false;
                Menu_Print_PrintDeliveryNote.IsEnabled = false;
            }
        }

        private void DeliveryPartList_SelectionChanged(object? sender, SelectionChangedEventArgs? evt) {
            RefreshInputs();
            if (DeliveryPartList.SelectedItem is DeliveryPart p) {
                AbwertenButton.IsEnabled = p.QualId != "WEI";
                EditDeliveryButton.IsEnabled = true;
                ExtractDeliveryPartButton.IsEnabled = !IsCreating;
                DeleteDeliveryPartButton.IsEnabled = DeliveryList.SelectedItem is Delivery { Parts.Count: > 1 } && !IsCreating;
            } else {
                AbwertenButton.IsEnabled = false;
                EditDeliveryButton.IsEnabled = false;
                ExtractDeliveryPartButton.IsEnabled = false;
                DeleteDeliveryPartButton.IsEnabled = false;
            }
        }

        private void MgNrInput_TextChanged(object sender, TextChangedEventArgs evt) {
            var valid = InputTextChanged((TextBox)sender, Validator.CheckMgNr);
            MemberInput.SelectedItem = valid ? Context.Members.Find(int.Parse(MgNrInput.Text)) : null;
        }

        private void MgNrInput_LostFocus(object sender, RoutedEventArgs evt) {
            var valid = InputLostFocus((TextBox)sender, Validator.CheckMgNr);
            MemberInput.SelectedItem = valid ? Context.Members.Find(int.Parse(MgNrInput.Text)) : null;
        }

        private void MemberInput_SelectionChanged(object? sender, SelectionChangedEventArgs? evt) {
            var m = MemberInput.SelectedItem as Member;
            if (m != null) MgNrInput.Text = m.MgNr.ToString();
            MemberAddressField.Text = m?.FullAddress;
            if (m == null) {
                UnsetDefaultValue(WineKgInput);
                WineKgInput.SelectedIndex = 0;
            } else {
                SetDefaultValue(WineKgInput, m.DefaultKg);
                WineKgInput.SelectedItem = m.DefaultKg;
            }
        }

        private void EmptyScale() {
            var scale = App.Scales.Where(s => s.ScaleId == ScaleId).FirstOrDefault();
            if (scale is not ICommandScale cs) return;
            cs.Empty();
        }

        private async void NewDeliveryPartButton_Click(object sender, RoutedEventArgs evt) {
            FinishButton.IsEnabled = false;
            NewDeliveryPartButton.IsEnabled = false;
            NewDeliveryPartButton.Cursor = Cursors.Wait;
            DeliveryPartList.IsEnabled = false;
            var p = await UpdateDeliveryPart(DeliveryList.SelectedItem as Delivery, DeliveryPartList.SelectedItem as DeliveryPart);
            EmptyScale();
            await RefreshDeliveryList();
            await RefreshDeliveryParts();
            NewDeliveryPartButton.Cursor = null;
            DeliveryList.SelectedItem = p?.Delivery;
            DeliveryPartList.SelectedItem = null;
            RefreshInputs();
            InitialInputs();
        }

        private async void FinishButton_Click(object sender, RoutedEventArgs evt) {
            FinishButton.IsEnabled = false;
            NewDeliveryPartButton.IsEnabled = false;
            FinishButton.Cursor = Cursors.Wait;
            DeliveryPartList.IsEnabled = false;
            var p = await UpdateDeliveryPart(DeliveryList.SelectedItem as Delivery, DeliveryPartList.SelectedItem as DeliveryPart);
            EmptyScale();
            await RefreshDeliveryList();
            await RefreshDeliveryParts();
            if (p?.Delivery != null) {
                Mouse.OverrideCursor = Cursors.AppStarting;
                try {
                    using var doc = new DeliveryNote(p.Delivery, Context);
                    await doc.Generate();
                    if (App.Config.Debug) {
                        doc.Show();
                    } else {
                        await doc.Print(2);
                    }
                } catch (Exception exc) {
                    MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error);
                }
                Mouse.OverrideCursor = null;
            }
            FinishButton.Cursor = null;
            DeliveryList.SelectedItem = null;
            await RenewContext();
            RefreshInputs();
            InitInputs();
        }

        private async void CancelCreatingButton_Click(object sender, RoutedEventArgs evt) {
            if (IsCreating && HasChanged) {
                var r = MessageBox.Show("Soll der Vorgang wirklich abgebrochen werden?", "Abbrechen bestätigen",
                    MessageBoxButton.YesNo, MessageBoxImage.Warning, MessageBoxResult.No);
                if (r != MessageBoxResult.Yes) return;
            }

            var attrList = await Context.WineAttributes.OrderBy(a => a.Name).Cast<object>().ToListAsync();
            attrList.Insert(0, new NullItem(""));
            ControlUtils.RenewItemsSource(AttributeInput, attrList, i => (i as WineAttr)?.AttrId, null, ControlUtils.RenewSourceDefault.First);
            ControlUtils.RenewItemsSource(MemberInput, await Context.Members.Where(m => m.IsActive || !IsReceipt).OrderBy(m => m.FamilyName).ThenBy(m => m.GivenName).ToListAsync(), i => (i as Member)?.MgNr);
            if (DeliveryList.SelectedItem is not Delivery d) {
                // switch away from creating mode
                IsCreating = false;
                IsEditing = false;
                DeliveryList.IsEnabled = true;
                DeliveryPartList.IsEnabled = true;
                DisableWeighingButtons();
                HideFinishNewPartDeliveryCancelButtons();
                ShowNewEditDeleteButtons();
                await RenewContext();
                RefreshInputs();
                ClearInputStates();
                LockInputs();
                UnlockSearchInputs();
            } else {
                // switch to last delivery part
                DeliveryPartList.IsEnabled = true;
                DeliveryPartList.SelectedItem = d.FilteredParts.Last();
            }
        }

        private async void NewDeliveryButton_Click(object? sender, RoutedEventArgs? evt) {
            TodayOnlyInput.IsChecked = true;
            SearchInput.Text = "";
            var attrList = await Context.WineAttributes.Where(a => a.IsActive).OrderBy(a => a.Name).Cast<object>().ToListAsync();
            attrList.Insert(0, new NullItem(""));
            ControlUtils.RenewItemsSource(AttributeInput, attrList, i => (i as WineAttr)?.AttrId, null, ControlUtils.RenewSourceDefault.First);
            ControlUtils.RenewItemsSource(MemberInput, await Context.Members.Where(m => m.IsActive || !IsReceipt).OrderBy(m => m.FamilyName).ThenBy(m => m.GivenName).ToListAsync(), i => (i as Member)?.MgNr);
            IsCreating = true;
            DeliveryList.IsEnabled = false;
            DeliveryPartList.IsEnabled = false;
            EnableWeighingButtons();
            DeliveryList.SelectedItem = null;
            HideNewEditDeleteButtons();
            UnlockInputs();
            InitInputs();
            ShowFinishNewPartDeliveryCancelButtons();
            LockSearchInputs();
        }

        private async void AbwertenButton_Click(object sender, RoutedEventArgs evt) {
            if (DeliveryPartList.SelectedItem is not DeliveryPart p) return;
            var res = Utils.ShowAbwertenDialog($"{p.Delivery.LsNr}/{p.DPNr}", p.Delivery.Member.AdministrativeName, p.Weight);
            EntityEntry<DeliveryPart>? entry1 = null, entry2 = null;
            try {
                if (res == null || res <= 0)
                    return;
                Mouse.OverrideCursor = Cursors.AppStarting;
                ClearOriginalValues();
                if (res >= p.Weight) {
                    ControlUtils.SelectComboBoxItem(WineQualityLevelInput, q => (q as WineQualLevel)?.QualId, "WEI");
                    ControlUtils.SelectComboBoxItem(WineOriginInput, o => (o as WineOrigin)?.HkId, "OEST");
                    p.QualId = "WEI";
                    p.HkId = "OEST";
                    entry1 = Context.Update(p);
                } else {
                    var w = p.Weight - res.Value;
                    WeightInput.Text = $"{w:N0}";
                    p.Weight = w;
                    entry1 = Context.Update(p);

                    var d = p.Delivery;
                    var p2 = Context.CreateProxy<DeliveryPart>();
                    var values = Context.Entry(p).CurrentValues;
                    Context.Entry(p2).CurrentValues.SetValues(values);
                    p2.DPNr = await Context.NextDPNr(d.Year, d.DId);
                    p2.Weight = res.Value;
                    p2.QualId = "WEI";
                    p2.HkId = "OEST";
                    entry2 = await Context.AddAsync(p2);

                    await Context.UpdateDeliveryPartModifiers(p2, p.Modifiers);
                }
                await Context.SaveChangesAsync();
                await RefreshDeliveryParts();
                Mouse.OverrideCursor = null;
                FinishInputFilling();
            } catch (Exception exc) {
                if (entry1 != null) {
                    entry1.State = EntityState.Detached;
                    await entry1.ReloadAsync();
                }
                if (entry2 != null) {
                    entry2.State = EntityState.Detached;
                    await entry2.ReloadAsync();
                }
                var str = "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\n" + exc.Message;
                if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
                MessageBox.Show(str, "Lieferung aktualisieren", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        private void WeighingManualButton_Click(object sender, RoutedEventArgs evt) {
            var res = Utils.ShowManualWeighingDialog(LastScaleError);
            if (res == null) return;
            WeightInput.Text = $"{res?.Item1:N0}";
            ManualWeighingInput.IsChecked = true;
            ManualWeighingReason = res?.Item2;
            ScaleId = null;
            WeighingId = null;
        }

        private void EditDeliveryButton_Click(object sender, RoutedEventArgs evt) {
            if (DeliveryPartList.SelectedItem == null)
                return;

            IsEditing = true;
            DeliveryList.IsEnabled = false;
            DeliveryPartList.IsEnabled = false;

            HideNewEditDeleteButtons();
            ShowSaveResetCancelButtons();
            UnlockInputs();
            LockSearchInputs();

            AbwertenButton.IsEnabled = false;
            ExtractDeliveryPartButton.IsEnabled = false;
            DeleteDeliveryPartButton.IsEnabled = false;
        }

        private async void DeleteDeliveryButton_Click(object sender, RoutedEventArgs evt) {
            if (DeliveryList.SelectedItem is not Delivery d)
                return;

            var r = MessageBox.Show(
                $"Soll die Lieferung {d.LsNr} ({d.Member.AdministrativeName}, MgNr. {d.Member.MgNr}) wirklich unwiderruflich gelöscht werden?",
                "Lieferung löschen", MessageBoxButton.YesNo, MessageBoxImage.Warning, MessageBoxResult.No);
            if (r == MessageBoxResult.Yes) {
                Mouse.OverrideCursor = Cursors.AppStarting;
                Context.Remove(d);
                await Context.SaveChangesAsync();
                await RefreshDeliveryList();
                await RefreshDeliveryParts();
                Mouse.OverrideCursor = null;
            }
        }

        private async void SaveButton_Click(object sender, RoutedEventArgs evt) {
            SaveButton.IsEnabled = false;
            SaveButton.Cursor = Cursors.Wait;

            IsEditing = false;
            IsCreating = false;
            DeliveryList.IsEnabled = true;
            DeliveryPartList.IsEnabled = true;

            DeliveryPart p = await UpdateDeliveryPart(DeliveryList.SelectedItem as Delivery, DeliveryPartList.SelectedItem as DeliveryPart);

            SaveButton.Cursor = null;

            HideSaveResetCancelButtons();
            ShowNewEditDeleteButtons();
            LockInputs();
            UnlockSearchInputs();
            FinishInputFilling();
            await RefreshDeliveryList();
            await RefreshDeliveryParts();
            RefreshInputs();

            AbwertenButton.IsEnabled = p.QualId != "WEI";
            ExtractDeliveryPartButton.IsEnabled = DeliveryPartList.SelectedItem != null && !IsCreating;
            DeleteDeliveryPartButton.IsEnabled = DeliveryList.SelectedItem is Delivery { Parts.Count: > 1 } && !IsCreating;
        }

        private void ResetButton_Click(object sender, RoutedEventArgs evt) {
            if (IsEditing) {
                RefreshInputs();
            } else if (IsCreating) {
                ClearInputs();
                InitInputs();
            }
            UpdateButtons();
        }

        private void CancelButton_Click(object sender, RoutedEventArgs evt) {
            IsEditing = false;
            IsCreating = false;
            DeliveryList.IsEnabled = true;
            DeliveryPartList.IsEnabled = true;

            HideSaveResetCancelButtons();
            ShowNewEditDeleteButtons();
            RefreshInputs();
            LockInputs();
            UnlockSearchInputs();

            AbwertenButton.IsEnabled = DeliveryPartList.SelectedItem is DeliveryPart p && p.QualId != "WEI";
            ExtractDeliveryPartButton.IsEnabled = DeliveryPartList.SelectedItem != null && !IsCreating;
            DeleteDeliveryPartButton.IsEnabled = DeliveryList.SelectedItem is Delivery { Parts.Count: > 1 } && !IsCreating;
        }

        private async void ExtractDeliveryPartButton_Click(object sender, RoutedEventArgs evt) {
            if (DeliveryPartList.SelectedItem is not DeliveryPart p)
                return;

            var delivery = p.Delivery;
            var day = delivery.Date;
            var count = delivery.Parts.Count;

            if (delivery.Time <= new TimeOnly(3, 0))
                day = day.AddDays(-1);
            var lsnrs = await Context.Deliveries
                .Where(d => d.ZwstId == delivery.ZwstId)
                .Where(d => (d.DateString == day.ToString("yyyy-MM-dd") && (d.TimeString == null || d.TimeString.CompareTo("03:00:00") > 0)) ||
                            (d.DateString == day.AddDays(1).ToString("yyyy-MM-dd") && (d.TimeString == null || d.TimeString.CompareTo("03:00:00") <= 0)))
                .Where(d => d.LsNr != delivery.LsNr)
                .OrderBy(d => d.LsNr)
                .Select(d => d.LsNr)
                .ToListAsync();

            var res = Utils.ShowDeliveryExtractionDialog($"{delivery.LsNr}/{p.DPNr}", delivery.Member.AdministrativeName, count == 1, lsnrs);
            if (res == null) return;
            EntityEntry<Delivery>? entry = null;
            try {
                Delivery? d = null;
                Mouse.OverrideCursor = Cursors.AppStarting;
                if (res == "new") {
                    d = Context.CreateProxy<Delivery>();
                    d.Date = delivery.Date;
                    d.Time = delivery.Time;
                    d.Year = p.Year;
                    d.DId = await Context.NextDId(d.Year);
                    d.LNr = await Context.NextLNr(d.Date);
                    d.ZwstId = delivery.ZwstId;
                    d.MgNr = delivery.MgNr;
                    d.Comment = delivery.Comment;
                    d.LsNr = Utils.GenerateLsNr(d);
                    entry = await Context.AddAsync(d);
                    await Context.SaveChangesAsync();
                } else {
                    d = await Context.Deliveries.Where(d => d.LsNr == res).FirstOrDefaultAsync();
                }
                if (d == null) return;

                await Context.Database.ExecuteSqlAsync($"UPDATE delivery_part SET year = {d.Year}, did = {d.DId}, dpnr = {await Context.NextDPNr(d.Year, d.DId)} WHERE (year, did, dpnr) = ({p.Year}, {p.DId}, {p.DPNr})");
                Context.Entry(p).State = EntityState.Detached;
                if (count == 1) {
                    await Context.Database.ExecuteSqlAsync($"DELETE FROM delivery WHERE (year, did) = ({delivery.Year}, {delivery.DId})");
                    Context.Entry(delivery).State = EntityState.Detached;
                }
                await Context.SaveChangesAsync();
                await Context.Entry(p).ReloadAsync();
                await Context.Entry(delivery).ReloadAsync();

                Mouse.OverrideCursor = null;
                await RefreshDeliveryList();
                DeliveryList.SelectedItem = d;
            } catch (Exception exc) {
                if (entry != null) {
                    entry.State = EntityState.Detached;
                    await entry.ReloadAsync();
                }
                var str = "Der Eintrag konnte nicht in der Datenbank aktualisiert werden!\n\n" + exc.Message;
                if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message;
                MessageBox.Show(str, "Lieferung aktualisieren", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        private async void DeleteDeliveryPartButton_Click(object sender, RoutedEventArgs evt) {
            if (DeliveryPartList.SelectedItem is not DeliveryPart p)
                return;

            var r = MessageBox.Show(
                $"Soll die Teillieferung Nr. {p.DPNr} wirklich unwiderruflich gelöscht werden?",
                "Lieferung löschen", MessageBoxButton.YesNo, MessageBoxImage.Warning, MessageBoxResult.No);
            if (r == MessageBoxResult.Yes) {
                Mouse.OverrideCursor = Cursors.AppStarting;
                Context.Remove(p);
                await Context.SaveChangesAsync();
                await RefreshDeliveryParts();
                Mouse.OverrideCursor = null;
            }
        }

        private void ShowSaveResetCancelButtons() {
            SaveButton.IsEnabled = false;
            ResetButton.IsEnabled = false;
            CancelButton.IsEnabled = true;
            SaveButton.Visibility = Visibility.Visible;
            ResetButton.Visibility = Visibility.Visible;
            CancelButton.Visibility = Visibility.Visible;
        }

        private void HideSaveResetCancelButtons() {
            SaveButton.IsEnabled = false;
            ResetButton.IsEnabled = false;
            CancelButton.IsEnabled = false;
            SaveButton.Visibility = Visibility.Hidden;
            ResetButton.Visibility = Visibility.Hidden;
            CancelButton.Visibility = Visibility.Hidden;
        }

        private void ShowNewEditDeleteButtons() {
            NewDeliveryButton.IsEnabled = IsReceipt;
            AbwertenButton.IsEnabled = DeliveryPartList.SelectedItem is DeliveryPart p && p.QualId == "WEI";
            EditDeliveryButton.IsEnabled = DeliveryPartList.SelectedItem != null;
            DeleteDeliveryButton.IsEnabled = DeliveryList.SelectedItem != null;
            NewDeliveryButton.Visibility = IsReceipt ? Visibility.Visible : Visibility.Hidden;
            AbwertenButton.Visibility = !IsReceipt ? Visibility.Visible : Visibility.Hidden;
            EditDeliveryButton.Visibility = Visibility.Visible;
            DeleteDeliveryButton.Visibility = Visibility.Visible;
        }

        private void HideNewEditDeleteButtons() {
            NewDeliveryButton.IsEnabled = false;
            AbwertenButton.IsEnabled = false;
            EditDeliveryButton.IsEnabled = false;
            DeleteDeliveryButton.IsEnabled = false;
            NewDeliveryButton.Visibility = Visibility.Hidden;
            AbwertenButton.Visibility = Visibility.Hidden;
            EditDeliveryButton.Visibility = Visibility.Hidden;
            DeleteDeliveryButton.Visibility = Visibility.Hidden;
        }

        private void ShowFinishNewPartDeliveryCancelButtons() {
            FinishButton.IsEnabled = false;
            NewDeliveryPartButton.IsEnabled = false;
            CancelCreatingButton.IsEnabled = true;
            FinishButton.Visibility = Visibility.Visible;
            NewDeliveryPartButton.Visibility = Visibility.Visible;
            CancelCreatingButton.Visibility = Visibility.Visible;
        }

        private void HideFinishNewPartDeliveryCancelButtons() {
            FinishButton.IsEnabled = false;
            NewDeliveryPartButton.IsEnabled = false;
            CancelCreatingButton.IsEnabled = false;
            FinishButton.Visibility = Visibility.Hidden;
            NewDeliveryPartButton.Visibility = Visibility.Hidden;
            CancelCreatingButton.Visibility = Visibility.Hidden;
        }

        private void LockSearchInputs() {
            SearchInput.IsEnabled = false;
            SeasonInput.IsEnabled = false;
            TodayOnlyInput.IsEnabled = false;
            AllSeasonsInput.IsEnabled = false;
        }

        private void UnlockSearchInputs() {
            SearchInput.IsEnabled = true;
            SeasonInput.IsEnabled = true;
            TodayOnlyInput.IsEnabled = true;
            AllSeasonsInput.IsEnabled = (Member != null);
        }

        new protected void UnlockInputs() {
            base.UnlockInputs();
            if (WineQualityLevelInput.SelectedItem != null && WineKgInput.SelectedItem != null)
                WineOriginInput.IsEnabled = false;
            if (WineKgInput.SelectedItem == null)
                WineRdInput.IsEnabled = false;
            WeightInput.TextBox.IsReadOnly = true;
            AbgewertetInput.IsEnabled = false;
            ManualWeighingInput.IsEnabled = false;
            LsNrInput.IsReadOnly = true;
            DateInput.IsReadOnly = !Menu_Settings_EnableFreeEditing.IsChecked;
            TimeInput.IsReadOnly = !Menu_Settings_EnableFreeEditing.IsChecked;
            BranchInput.IsEnabled = Menu_Settings_EnableFreeEditing.IsChecked;
        }

        private void DisableWeighingButtons() {
            WeighingManualButton.IsEnabled = false;
            WeighingAButton.IsEnabled = false;
            WeighingBButton.IsEnabled = false;
            WeighingCButton.IsEnabled = false;
            WeighingDButton.IsEnabled = false;
        }

        private void EnableWeighingButtons() {
            WeighingManualButton.IsEnabled = true;
            var n = App.CommandScales.Count;
            WeighingAButton.IsEnabled = n > 0 && App.CommandScales[0].IsReady;
            WeighingBButton.IsEnabled = n > 1 && App.CommandScales[1].IsReady;
            WeighingCButton.IsEnabled = n > 2 && App.CommandScales[2].IsReady;
            WeighingDButton.IsEnabled = n > 3 && App.CommandScales[3].IsReady;
        }

        private async Task UpdateLsNr() {
            if (DateInput.Text == "" || BranchInput.SelectedItem == null) {
                LsNrInput.Text = "";
            } else {
                try {
                    var branch = (Branch)BranchInput.SelectedItem;
                    var date = DateOnly.ParseExact(DateInput.Text, "dd.MM.yyyy");
                    var lnr = await Context.NextLNr(date);
                    LsNrInput.Text = Utils.GenerateLsNr(date, branch.ZwstId, lnr);
                } catch {
                    LsNrInput.Text = "";
                }
            }
        }

        private new async void DateInput_TextChanged(object sender, TextChangedEventArgs evt) {
            base.DateInput_TextChanged(sender, evt);
            if (IsEditing || IsCreating) await UpdateLsNr();
        }

        private async void BranchInput_SelectionChanged(object sender, SelectionChangedEventArgs evt) {
            base.ComboBox_SelectionChanged(sender, evt);
            if (IsEditing || IsCreating) {
                await UpdateLsNr();
                InitialDefaultInputs();
            }
        }

        private void UpdateWineVariety(bool valid) {
            if (valid) {
                var text = SortIdInput.Text;
                WineVarietyInput.SelectedItem = Context.WineVarieties.Find(text[0..2]);
                if (text.Length >= 3) {
                    ControlUtils.SelectComboBoxItem(AttributeInput, Context.WineAttributes.Find(text[2..]), a => (a as WineAttr)?.AttrId);
                    ControlUtils.SelectComboBoxItem(CultivationInput, Context.WineCultivations.Find(text[2..]), i => (i as WineCult)?.CultId);
                    SortIdInput.Text = text[0..2];
                }
            } else {
                WineVarietyInput.SelectedItem = null;
                AttributeInput.SelectedIndex = 0;
                CultivationInput.SelectedIndex = 0;
            }
        }

        private void SortIdInput_TextChanged(object sender, TextChangedEventArgs evt) {
            UpdateWineVariety(InputTextChanged((TextBox)sender, Validator.CheckSortId));
        }

        private void SortIdInput_LostFocus(object sender, RoutedEventArgs evt) {
            UpdateWineVariety(InputLostFocus((TextBox)sender, Validator.CheckSortId));
        }

        private void WineVarietyInput_SelectionChanged(object sender, SelectionChangedEventArgs evt) {
            if (WineVarietyInput.SelectedItem is WineVar s)
                    SortIdInput.Text = s.SortId;
        }

        private void UpdateWineQualityLevels() {
            if (!GetInputValid(GradationKmwInput)) {
                UnsetDefaultValue(WineQualityLevelInput);
                WineQualityLevelInput.ItemsSource = Context.WineQualityLevels.Where(q => q.QualId == "WEI").ToList();
                return;
            }
            var kmw = double.Parse(GradationKmwInput.Text);
            WineQualityLevelInput.ItemsSource = Context.WineQualityLevels.Where(q => q.MinKmw == null || q.MinKmw <= kmw).ToList();
            var qual = Context.GetWineQualityLevel(kmw).GetAwaiter().GetResult();
            SetDefaultValue(WineQualityLevelInput, qual);
            if (WineQualityLevelInput.SelectedItem == null || (WineQualityLevelInput.SelectedItem is WineQualLevel selected && !selected.IsPredicate)) {
                WineQualityLevelInput.SelectedItem = qual;
            }
        }

        private void UpdateGradationKmw() {
            IsUpdatingGradation = true;
            var caret = GradationKmwInput.TextBox.CaretIndex;
            GradationKmwInput.Text = $"{Utils.OeToKmw(double.Parse(GradationOeInput.Text)):#.0}";
            GradationKmwInput.TextBox.CaretIndex = caret;
            IsUpdatingGradation = false;
        }

        private void UpdateGradationOe() {
            IsUpdatingGradation = true;
            var caret = GradationOeInput.TextBox.CaretIndex;
            GradationOeInput.Text = $"{Utils.KmwToOe(double.Parse(GradationKmwInput.Text)):#}";
            GradationOeInput.TextBox.CaretIndex = caret;
            IsUpdatingGradation = false;
        }

        private void GradationOeInput_TextChanged(object sender, TextChangedEventArgs evt) {
            var valid = InputTextChanged((TextBox)sender, Validator.CheckGradatoinOe);
            if (!IsUpdatingGradation) {
                if (valid) UpdateGradationKmw();
                else if (GradationOeInput.Text.Length == 0) GradationKmwInput.Text = "";
                if (valid || GradationOeInput.Text.Length == 0) UpdateWineQualityLevels();
            }
        }

        private void GradationOeInput_LostFocus(object sender, RoutedEventArgs evt) {
            InputLostFocus((TextBox)sender, Validator.CheckGradatoinOe);
        }

        private void GradationKmwInput_TextChanged(object sender, TextChangedEventArgs evt) {
            var valid = InputTextChanged((TextBox)sender, Validator.CheckGradationKmw);
            if (!IsUpdatingGradation) {
                if (valid) UpdateGradationOe();
                else if (GradationKmwInput.Text.Length == 0) GradationOeInput.Text = "";
                if (valid || GradationKmwInput.Text.Length == 0) UpdateWineQualityLevels();
            }
        }

        private void GradationKmwInput_LostFocus(object sender, RoutedEventArgs evt) {
            if (GradationKmwInput.Text.EndsWith(',')) GradationKmwInput.Text += "0";
            InputLostFocus((TextBox)sender, Validator.CheckGradationKmw);
            if (GradationKmwInput.Text.Length != 0 && !GradationKmwInput.Text.Contains(','))
                GradationKmwInput.Text += ",0";
        }

        private void AttributeInput_SelectionChanged(object sender, SelectionChangedEventArgs evt) {

        }

        private void CultivationInput_SelectionChanged(object sender, SelectionChangedEventArgs evt) {

        }

        private void ModifiersInput_SelectionChanged(object sender, ItemSelectionChangedEventArgs evt) {
            if (!IsEditing && !IsCreating) return;
            var mod = ModifiersInput.SelectedItems.Cast<Modifier>();
            var source = ModifiersInput.ItemsSource.Cast<Modifier>();
            if (App.Client.IsMatzen) {
                var kl = mod.Where(m => m.Name.StartsWith("Klasse "));
                if (kl.Count() > 1) {
                    foreach (var r in kl.Take(kl.Count() - 1)) ModifiersInput.SelectedItems.Remove(r);
                }
            }
        }

        private void LesewagenInput_Changed(object sender, RoutedEventArgs evt) {
            if (!IsEditing && !IsCreating) return;
            var mod = ModifiersInput.SelectedItems.Cast<Modifier>();
            var source = ModifiersInput.ItemsSource.Cast<Modifier>();
            var lw = LesewagenInput.IsChecked == true;
            if (App.Client.IsMatzen) {
                var kl = mod.Where(m => m.Name.StartsWith("Klasse ")).Select(m => m.ModId).LastOrDefault("A")[0];
                if (lw) kl++; else kl--;
                var newKl = source.Where(m => m.ModId == kl.ToString()).FirstOrDefault();
                if (newKl != null) ModifiersInput.SelectedItems.Add(newKl);
            }
            CheckBox_Changed(sender, evt);
        }

        private void UpdateWineOrigin() {
            var qual = WineQualityLevelInput.SelectedItem as WineQualLevel;
            var kg = (WineKgInput.SelectedItem as AT_Kg)?.WbKg;
            if (qual == null || kg == null) {
                WineOriginInput.IsEnabled = (IsEditing || IsCreating);
                UnsetDefaultValue(WineOriginInput);
                return;
            }
            WineOriginInput.IsEnabled = false;
            var o = kg.Origin;
            while (o != null && o.Level > qual.OriginLevel) o = o.Parent;
            SetDefaultValue(WineOriginInput, o);
            WineOriginInput.SelectedItem = o;
        }

        private void WineQualityLevelInput_SelectionChanged(object sender, SelectionChangedEventArgs evt) {
            UpdateWineOrigin();
            UpdateAbgewertet();
        }

        private void UpdateRdInput() {
            if (WineKgInput.SelectedItem is AT_Kg kg) {
                var list = Context.WbRde.Where(r => r.KgNr == kg.KgNr).OrderBy(r => r.Name).Cast<object>().ToList();
                list.Insert(0, new NullItem());
                ControlUtils.RenewItemsSource(WineRdInput, list, i => ((i as WbRd)?.KgNr, (i as WbRd)?.RdNr));
                if (WineRdInput.SelectedItem == null) WineRdInput.SelectedIndex = 0;
                WineRdInput.IsEnabled = (IsEditing || IsCreating) && list.Count > 1;
            } else {
                WineRdInput.ItemsSource = null;
                WineRdInput.IsEnabled = false;
            }
        }

        private void WineKgInput_SelectionChanged(object sender, SelectionChangedEventArgs evt) {
            UpdateWineOrigin();
            UpdateRdInput();
        }

        private void UpdateAbgewertet() {
            if (!GetInputValid(GradationKmwInput))
                return;
            var qual = WineQualityLevelInput.SelectedItem as WineQualLevel;
            if (qual == null) {
                AbgewertetInput.IsChecked = false;
                return;
            }
            var defQual = Context.GetWineQualityLevel(double.Parse(GradationKmwInput.Text)).GetAwaiter().GetResult();
            AbgewertetInput.IsChecked = !qual.IsPredicate && defQual != qual;
        }

        private void WeightInput_TextChanged(object sender, TextChangedEventArgs evt) {
            InputTextChanged((TextBox)sender);
        }

        private void TemperatureAcidInput_TextChanged(object sender, TextChangedEventArgs evt) {
            InputTextChanged((TextBox)sender, Validator.CheckDecimal((TextBox)sender, false, 2, 1));
        }

        private void TemperatureAcidInput_LostFocus(object sender, RoutedEventArgs evt) {
            if (sender is not TextBox tb) return;
            if (tb.Text.Length > 0) {
                if (!tb.Text.Contains(',')) tb.Text += ",0";
                if (tb.Text.EndsWith(',')) tb.Text += "0";
            }
            InputLostFocus(tb, Validator.CheckDecimal(tb, false, 2, 1));
        }

        private void GerebeltGewogenInput_Changed(object sender, RoutedEventArgs evt) {
            if (!App.Client.HasNetWeighing(BranchInput.SelectedValue as Branch)) {
                HandPickedInput.IsChecked = !GerebeltGewogenInput.IsChecked;
            }
            CheckBox_Changed(sender, evt);
        }

        private void HandPickedInput_Changed(object sender, RoutedEventArgs evt) {
            if (!App.Client.HasNetWeighing(BranchInput.SelectedValue as Branch)) {
                GerebeltGewogenInput.IsChecked = !HandPickedInput.IsChecked;
            }
            CheckBox_Changed(sender, evt);
        }

        private void MemberReferenceButton_Click(object sender, RoutedEventArgs evt) {
            if (MemberInput.SelectedItem is not Member m) return;
            App.FocusMember(m.MgNr);
        }
    }
}