package de.assecutor.votianlt.pages.view;

import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.checkbox.Checkbox;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.dialog.Dialog;
import com.vaadin.flow.component.UI;

import com.vaadin.flow.component.datepicker.DatePicker;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.timepicker.TimePicker;
import com.vaadin.flow.component.html.H3;
import com.vaadin.flow.component.html.Main;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.progressbar.ProgressBar;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.Binder;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.textfield.TextArea;
import com.vaadin.flow.router.HasDynamicTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.theme.lumo.LumoUtility;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import de.assecutor.votianlt.model.Job;
import de.assecutor.votianlt.model.JobServiceSelection;
import de.assecutor.votianlt.model.task.BarcodeTask;
import de.assecutor.votianlt.model.task.BaseTask;
import de.assecutor.votianlt.model.task.CommentTask;
import de.assecutor.votianlt.model.task.ConfirmationTask;
import de.assecutor.votianlt.model.task.PhotoTask;
import de.assecutor.votianlt.model.task.SignatureTask;
import de.assecutor.votianlt.model.task.TodoListTask;
import de.assecutor.votianlt.pages.service.AddJobService;
import de.assecutor.votianlt.pages.service.CustomerService;
import de.assecutor.votianlt.pages.service.AddCustomerService;
import de.assecutor.votianlt.pages.service.AddressValidationService;
import de.assecutor.votianlt.model.Customer;
import de.assecutor.votianlt.pages.service.AppUserService;
import de.assecutor.votianlt.model.AppUser;
import de.assecutor.votianlt.pages.service.TaskTemplateService;
import de.assecutor.votianlt.model.TaskTemplate;
import de.assecutor.votianlt.model.User;
import de.assecutor.votianlt.security.SecurityService;
import de.assecutor.votianlt.model.Service;
import de.assecutor.votianlt.repository.ServiceRepository;
import com.vaadin.flow.component.grid.Grid;
import java.math.BigDecimal;
import java.math.RoundingMode;

import jakarta.annotation.security.RolesAllowed;
import lombok.extern.slf4j.Slf4j;
import de.assecutor.votianlt.model.CargoItem;
import de.assecutor.votianlt.model.DeliveryStation;
import de.assecutor.votianlt.model.AddressValidationResult;
import de.assecutor.votianlt.model.RouteCalculationResult;
import de.assecutor.votianlt.pages.base.ui.component.DeliveryStationTile;
import de.assecutor.votianlt.pages.base.ui.component.StationTile;
import de.assecutor.votianlt.pages.base.ui.component.CustomerAddressLabelHelper;
import de.assecutor.votianlt.pages.base.ui.component.PickupStationDialog;
import de.assecutor.votianlt.pages.base.ui.component.DeliveryStationDialog;
import de.assecutor.votianlt.pages.base.ui.component.DialogStylingHelper;
import de.assecutor.votianlt.pages.base.ui.component.ViewToolbar;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;

@Route(value = "add_job", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
@RolesAllowed("USER")
@Slf4j
public class AddJobView extends Main implements HasDynamicTitle {
    private static final DateTimeFormatter PICKUP_PREVIEW_DATE_FORMATTER = DateTimeFormatter.ofPattern("dd.MM.yyyy");
    private static final DateTimeFormatter PICKUP_PREVIEW_TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm");

    private static final class SelectedServiceEntry {
        private final Service service;
        private Integer deliveryStationOrder;

        private SelectedServiceEntry(Service service, Integer deliveryStationOrder) {
            this.service = service;
            this.deliveryStationOrder = deliveryStationOrder;
        }

        public Service getService() {
            return service;
        }

        public Integer getDeliveryStationOrder() {
            return deliveryStationOrder;
        }

        public void setDeliveryStationOrder(Integer deliveryStationOrder) {
            this.deliveryStationOrder = deliveryStationOrder;
        }
    }

    private record RouteCalculationBundle(RouteCalculationResult totalRoute,
            Map<Integer, RouteCalculationResult> deliveryRoutes) {
    }

    @Override
    public String getPageTitle() {
        return getTranslation("page.title.job.create");
    }

    private final AddJobService addJobService;
    private final CustomerService customerService;
    private final AddCustomerService addCustomerService;
    private final AppUserService appUserService;
    private final TaskTemplateService taskTemplateService;
    private final SecurityService securityService;
    private final ServiceRepository serviceRepository;
    private final AddressValidationService addressValidationService;
    // Customer selection
    private ComboBox<String> customerSelection;

    // Pickup address fields
    private ComboBox<String> pickupCompany;
    private ComboBox<String> pickupSalutation;
    private TextField pickupFirstName;
    private TextField pickupLastName;
    private TextField pickupPhone;
    private String pickupMail;
    private TextField pickupStreet;
    private TextField pickupHouseNumber;
    private TextField pickupAddressAddition;
    private TextField pickupZip;
    private TextField pickupCity;
    private Checkbox savePickupAddress;
    private org.bson.types.ObjectId pickupCustomerId;
    private boolean pickupAddressDiffers;

    // Delivery stations as tiles in a 3x3 grid (max 7 delivery + 1 pickup + 1 plus
    // = 9)
    private final List<StationTile> deliveryStationTilesList = new ArrayList<>();
    private final List<DeliveryStation> deliveryStationsState = new ArrayList<>();
    private final List<Boolean> deliveryStationsSaveAddress = new ArrayList<>();
    private final List<org.bson.types.ObjectId> deliveryStationsCustomerId = new ArrayList<>();
    private final List<Boolean> deliveryStationsAddressDiffers = new ArrayList<>();
    private final List<String> deliveryStationsMailState = new ArrayList<>();
    private final List<Div> deliveryStationSlotList = new ArrayList<>();
    private final List<Span> deliveryStationDistanceChips = new ArrayList<>();
    private Div stationsGridContainer;
    private Div addStationButton;
    private Div addStationButtonSlot;
    private Div pickupStationSlot;
    private StationTile pickupTile;
    private static final int MAX_DELIVERY_STATIONS = 7;

    // Digital processing
    private Checkbox digitalProcessing;
    private ComboBox<AppUser> appUser;

    // Services for the job
    private Grid<SelectedServiceEntry> servicesGrid;
    private final List<SelectedServiceEntry> selectedServices = new ArrayList<>();
    private Span netTotalLabel;
    private Span grossTotalLabel;

    // Route distance display
    private VerticalLayout routeInfoBox;
    private Span routeDistanceLabel;
    private Span routeDurationLabel;

    // Manuelle Eingabe für Route (wenn keine Berechnung erfolgt)
    private VerticalLayout manualRouteInputBox;
    private com.vaadin.flow.component.textfield.NumberField manualDistanceInput;
    private com.vaadin.flow.component.textfield.IntegerField manualDurationInput;

    // Date picker fields for appointments
    private DatePicker pickupDate;

    // Time picker fields for appointments
    private TimePicker pickupTime;

    // Submit button
    private Button submitButton;
    private HorizontalLayout submitButtonLayout;

    // Stationen übernehmen Button und Bereich darunter
    private Button applyStationsButton;
    private VerticalLayout priceAndDetailsSection;

    // Backing list for cargo items to mirror UI rows
    private final List<CargoItem> cargoItemsState = new ArrayList<>();
    // Backing list for tasks per delivery station (stationIndex -> tasks)
    private final Map<Integer, List<BaseTask>> deliveryStationTasksState = new HashMap<>();
    private TextArea remarkArea;
    private final Binder<Job> binder = new Binder<>(Job.class);

    // Mapping für die Anzeige-Labels der Kunden zur Entität
    private final Map<String, Customer> customerLabelToEntity = new LinkedHashMap<>();

    // Available app users for the current user
    private List<AppUser> availableAppUsers;

    // Adressvalidierung
    private final Map<String, AddressValidationResult> addressValidationResults = new HashMap<>();
    private RouteCalculationResult routeCalculationResult;
    private boolean pickupAddressValidatedByGoogle;
    private final List<Boolean> deliveryStationsValidatedByGoogle = new ArrayList<>();
    private final Map<Integer, RouteCalculationResult> pickupToDeliveryRouteResults = new HashMap<>();

    public AddJobView(AddJobService addJobService, AddCustomerService addCustomerService,
            CustomerService customerService, AppUserService appUserService, TaskTemplateService taskTemplateService,
            SecurityService securityService, ServiceRepository serviceRepository,
            AddressValidationService addressValidationService) {
        this.addJobService = addJobService;
        this.addCustomerService = addCustomerService;
        this.customerService = customerService;
        this.appUserService = appUserService;
        this.taskTemplateService = taskTemplateService;
        this.securityService = securityService;
        this.serviceRepository = serviceRepository;
        this.addressValidationService = addressValidationService;
        initializeComponents();
        setupLayout();
        setupValidation();
        loadDraftIfExists();
        loadMandatoryServices();
    }

    private void initializeComponents() {
        // Customer selection
        customerSelection = new ComboBox<>(getTranslation("addjob.customer.label"));
        customerSelection.setPlaceholder(getTranslation("addjob.customer.placeholder"));
        customerSelection.setWidthFull();
        customerSelection.setRequiredIndicatorVisible(true);
        customerSelection.setAllowCustomValue(true);
        customerSelection.addCustomValueSetListener(event -> setCustomerSelectionValue(event.getDetail()));
        // Mit Kunden des angemeldeten Benutzers befüllen und Mapping aufbauen
        List<Customer> ownerCustomers = customerService.findAllForCurrentOwner();
        customerLabelToEntity.clear();
        for (Customer c : ownerCustomers) {
            CustomerAddressLabelHelper.putName(customerLabelToEntity, c, getTranslation("addjob.customer.unnamed"));
        }
        customerSelection.setItems(new ArrayList<>(customerLabelToEntity.keySet()));

        // Pickup address
        pickupCompany = new ComboBox<>(getTranslation("addjob.address.pickup.label"));
        pickupCompany.setPlaceholder(getTranslation("addjob.address.pickup.placeholder"));
        pickupCompany.setAllowCustomValue(true);
        setupCompanyAutocomplete(pickupCompany, true); // true für Pickup
        pickupSalutation = new ComboBox<>(getTranslation("addjob.address.salutation"));
        pickupSalutation.setItems(getTranslation("addjob.salutation.mr"), getTranslation("addjob.salutation.ms"),
                getTranslation("addjob.salutation.other"));
        pickupSalutation.setPlaceholder(getTranslation("addjob.address.salutation.placeholder"));
        pickupFirstName = new TextField(getTranslation("profile.firstname"));
        pickupFirstName.setPlaceholder(getTranslation("profile.firstname"));
        pickupFirstName.setRequiredIndicatorVisible(true);
        pickupLastName = new TextField(getTranslation("profile.lastname"));
        pickupLastName.setPlaceholder(getTranslation("profile.lastname"));
        pickupLastName.setRequiredIndicatorVisible(true);
        pickupPhone = new TextField(getTranslation("profile.phone"));
        pickupPhone.setPlaceholder(getTranslation("profile.phone"));
        pickupStreet = new TextField(getTranslation("profile.street"));
        pickupStreet.setPlaceholder(getTranslation("addjob.address.street.placeholder"));
        pickupStreet.setRequiredIndicatorVisible(true);
        pickupHouseNumber = new TextField(getTranslation("addjob.address.housenumber"));
        pickupHouseNumber.setPlaceholder(getTranslation("addjob.address.housenumber"));
        pickupHouseNumber.setRequiredIndicatorVisible(true);
        pickupAddressAddition = new TextField(getTranslation("profile.addressadd"));
        pickupAddressAddition.setPlaceholder(getTranslation("addjob.address.addition.placeholder"));
        pickupZip = new TextField(getTranslation("profile.zip"));
        pickupZip.setPlaceholder(getTranslation("profile.zip"));
        pickupZip.setRequiredIndicatorVisible(true);
        pickupCity = new TextField(getTranslation("addjob.address.city"));
        pickupCity.setPlaceholder(getTranslation("addjob.address.city.placeholder.pickup"));
        pickupCity.setRequiredIndicatorVisible(true);
        savePickupAddress = new Checkbox(getTranslation("addjob.address.save"));
        savePickupAddress.setValue(true);

        // Delivery station tiles will be created in createCustomerAndAddressesTab()

        // Digital processing - set value based on user's profile setting
        digitalProcessing = new Checkbox(getTranslation("profile.settings.digitalprocess"));
        // Get current user's digital processing preference from profile
        try {
            User currentUser = securityService.getCurrentDatabaseUser();
            digitalProcessing.setValue(currentUser.isDigitalProcessingEnabled());
        } catch (Exception e) {
            digitalProcessing.setValue(true); // Default to true if user not found
        }
        appUser = new ComboBox<>(getTranslation("addjob.appuser.label"));

        // Load app users for current user and set up the ComboBox
        availableAppUsers = appUserService.findByCurrentUser();
        appUser.setItems(availableAppUsers);
        appUser.setItemLabelGenerator(
                user -> user.getVorname() + " " + user.getNachname() + " (" + user.getEmail() + ")");
        appUser.setPlaceholder(getTranslation("addjob.appuser.placeholder"));

        // Services grid will be initialized in createCustomerAndAddressesTab()
        // Date/time picker fields for appointments (stored in AddJobView for binder, UI
        // is in PickupStationDialog)
        pickupTime = new TimePicker(getTranslation("addjob.appointment.time"));
        pickupTime.setLocale(java.util.Locale.GERMANY);
        pickupDate = new DatePicker(getTranslation("addjob.appointment.date"));
        pickupDate.setRequiredIndicatorVisible(true);
        pickupDate.setMin(LocalDate.now());
        pickupDate.setLocale(java.util.Locale.GERMANY); // Monday as first day of week
        pickupDate.setI18n(new DatePicker.DatePickerI18n().setFirstDayOfWeek(1) // 1 = Monday
                .setMonthNames(java.util.Arrays.asList("Januar", "Februar", "März", "April", "Mai", "Juni", "Juli",
                        "August", "September", "Oktober", "November", "Dezember"))
                .setWeekdays(java.util.Arrays.asList("Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag",
                        "Freitag", "Samstag"))
                .setWeekdaysShort(java.util.Arrays.asList("So", "Mo", "Di", "Mi", "Do", "Fr", "Sa")));

        // Submit button - initially disabled until all required fields are valid
        submitButton = new Button(getTranslation("addjob.button.submit"), event -> submit());
        submitButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
        submitButton.setEnabled(false);

        // Listener für Adressänderungen registrieren
        setupAddressChangeListeners();
    }

    // Testdaten entfernt

    private void setupLayout() {
        setSizeFull();
        addClassNames(LumoUtility.BoxSizing.BORDER, LumoUtility.Display.FLEX, LumoUtility.FlexDirection.COLUMN,
                LumoUtility.Padding.MEDIUM, LumoUtility.Gap.SMALL);
        addClassNames("form-page", "add-job-view");

        add(new ViewToolbar(getTranslation("addjob.title")));

        // Add content directly (no tabs)
        add(createCustomerAndAddressesTab());

        // Add submit button horizontally centered below the content
        submitButtonLayout = new HorizontalLayout();
        submitButtonLayout.setWidthFull();
        submitButtonLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
        submitButtonLayout.setPadding(true);
        submitButtonLayout.addClassName("form-shell");
        submitButtonLayout.add(submitButton);
        submitButtonLayout.setVisible(false);

        add(submitButtonLayout);
    }

    private Component createCustomerAndAddressesTab() {
        VerticalLayout tabContent = new VerticalLayout();
        tabContent.setWidthFull();
        tabContent.setPadding(true);
        tabContent.setSpacing(true);
        tabContent.addClassNames("form-shell", "form-card");

        // 3x3 Grid container for station tiles
        stationsGridContainer = new Div();
        stationsGridContainer.getStyle().set("padding", "var(--lumo-space-s)");
        stationsGridContainer.setWidthFull();
        stationsGridContainer.addClassName("stations-grid");

        // Pickup tile (always present)
        pickupTile = new StationTile(StationTile.StationType.PICKUP, 0, getTranslation("addjob.section.pickup"), false);
        pickupTile.setClickListener(tile -> openPickupDialog());
        pickupStationSlot = createStationSlot(pickupTile, null);
        stationsGridContainer.add(pickupStationSlot);

        // "+" add station button tile (must be created before addDeliveryStationTile)
        addStationButton = createAddStationButton();
        addStationButtonSlot = createStationSlot(addStationButton, null);

        // Add first delivery station tile (this will also add the "+" button)
        addDeliveryStationTile();

        // Setup focus listeners for customer selection field
        setupInputFieldFocusListeners();

        tabContent.add(stationsGridContainer);

        // Wrapper für alle Elemente nach Stationen (initial versteckt)
        priceAndDetailsSection = new VerticalLayout();
        priceAndDetailsSection.setWidthFull();
        priceAndDetailsSection.setPadding(false);
        priceAndDetailsSection.setSpacing(true);
        priceAndDetailsSection.setVisible(false);
        priceAndDetailsSection.addClassName("content-panel");

        // "Stationen übernehmen" Button
        applyStationsButton = new Button(getTranslation("addjob.stations.apply"));
        applyStationsButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
        applyStationsButton.setWidthFull();
        applyStationsButton.setEnabled(false);
        applyStationsButton.addClickListener(e -> handleApplyStations());
        tabContent.add(applyStationsButton);

        // Route Info Box
        routeInfoBox = new VerticalLayout();
        routeInfoBox.setPadding(true);
        routeInfoBox.setSpacing(true);
        routeInfoBox.getStyle().set("border", "1px solid var(--lumo-primary-color-50pct)");
        routeInfoBox.getStyle().set("border-radius", "var(--lumo-border-radius-m)");
        routeInfoBox.getStyle().set("background-color", "var(--lumo-primary-color-10pct)");
        routeInfoBox.setWidthFull();
        routeInfoBox.setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.STRETCH);
        routeInfoBox.setVisible(false); // Initial versteckt
        routeInfoBox.addClassName("route-card");

        H3 routeTitle = new H3(getTranslation("addjob.route.title"));
        routeTitle.getStyle().set("margin", "0");
        routeTitle.getStyle().set("color", "var(--lumo-primary-text-color)");

        HorizontalLayout routeRow = new HorizontalLayout();
        routeRow.setWidthFull();
        routeRow.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);
        routeRow.setAlignItems(FlexComponent.Alignment.CENTER);

        Span distanceLabel = new Span(getTranslation("addjob.route.distance") + ":");
        routeDistanceLabel = new Span("-");
        routeDistanceLabel.getStyle().set("font-weight", "bold");
        routeDistanceLabel.getStyle().set("font-size", "var(--lumo-font-size-l)");
        routeDistanceLabel.getStyle().set("color", "var(--lumo-primary-text-color)");

        routeRow.add(distanceLabel, routeDistanceLabel);

        HorizontalLayout durationRow = new HorizontalLayout();
        durationRow.setWidthFull();
        durationRow.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);
        durationRow.setAlignItems(FlexComponent.Alignment.CENTER);

        Span durationLabel = new Span(getTranslation("addjob.route.duration") + ":");
        routeDurationLabel = new Span("-");
        routeDurationLabel.getStyle().set("font-weight", "bold");
        routeDurationLabel.getStyle().set("color", "var(--lumo-secondary-text-color)");

        durationRow.add(durationLabel, routeDurationLabel);

        routeInfoBox.add(routeTitle, routeRow, durationRow);
        priceAndDetailsSection.add(routeInfoBox);

        // Manuelle Streckeneingabe (wenn keine Route berechnet wurde)
        manualRouteInputBox = new VerticalLayout();
        manualRouteInputBox.setPadding(true);
        manualRouteInputBox.setSpacing(true);
        manualRouteInputBox.getStyle().set("border", "1px solid var(--lumo-contrast-30pct)");
        manualRouteInputBox.getStyle().set("border-radius", "var(--lumo-border-radius-m)");
        manualRouteInputBox.getStyle().set("background-color", "var(--lumo-contrast-5pct)");
        manualRouteInputBox.setWidthFull();
        manualRouteInputBox.setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.STRETCH);
        manualRouteInputBox.setVisible(true); // Initial sichtbar
        manualRouteInputBox.addClassName("route-card");

        H3 manualRouteTitle = new H3(getTranslation("addjob.route.manual.title"));
        manualRouteTitle.getStyle().set("margin", "0");
        manualRouteTitle.getStyle().set("color", "var(--lumo-secondary-text-color)");

        HorizontalLayout manualInputRow = new HorizontalLayout();
        manualInputRow.setWidthFull();
        manualInputRow.setSpacing(true);

        manualDistanceInput = new com.vaadin.flow.component.textfield.NumberField(
                getTranslation("addjob.route.distance.km"));
        manualDistanceInput.setWidthFull();
        manualDistanceInput.setPlaceholder(getTranslation("addjob.route.distance.placeholder"));
        manualDistanceInput.setMin(0);
        manualDistanceInput.setStep(0.1);
        manualDistanceInput.setClearButtonVisible(true);
        manualDistanceInput.addValueChangeListener(e -> {
            // Preise aktualisieren wenn sich die Distanz ändert
            updatePriceSummary();
            if (servicesGrid != null) {
                servicesGrid.getDataProvider().refreshAll();
            }
        });

        manualDurationInput = new com.vaadin.flow.component.textfield.IntegerField(
                getTranslation("addjob.route.duration.min"));
        manualDurationInput.setWidthFull();
        manualDurationInput.setPlaceholder(getTranslation("addjob.route.duration.placeholder"));
        manualDurationInput.setMin(0);
        manualDurationInput.setStep(1);
        manualDurationInput.setClearButtonVisible(true);
        manualDurationInput.addValueChangeListener(e -> {
            updatePriceSummary();
            if (servicesGrid != null) {
                servicesGrid.getDataProvider().refreshAll();
            }
        });

        manualInputRow.add(manualDistanceInput, manualDurationInput);

        Span manualRouteHint = new Span(getTranslation("addjob.route.manual.hint"));
        manualRouteHint.getStyle().set("font-size", "var(--lumo-font-size-s)");
        manualRouteHint.getStyle().set("color", "var(--lumo-secondary-text-color)");
        manualRouteHint.getStyle().set("font-style", "italic");

        manualRouteInputBox.add(manualRouteTitle, manualInputRow, manualRouteHint);
        priceAndDetailsSection.add(manualRouteInputBox);

        // Leistungen
        H3 servicesTitle = new H3(getTranslation("addjob.services.title"));
        servicesTitle.getStyle().set("margin", "0");
        priceAndDetailsSection.add(servicesTitle);

        // Services Grid
        servicesGrid = new Grid<>();
        servicesGrid.setWidthFull();
        servicesGrid.setHeight("250px");
        servicesGrid.setItems(selectedServices);
        servicesGrid.addClassName("data-grid");

        servicesGrid.addColumn(entry -> entry.getService().getName()).setHeader(getTranslation("common.service"))
                .setSortable(true);
        servicesGrid.addColumn(this::formatSelectedServiceStationLabel)
                .setHeader(getTranslation("addjob.services.deliverystation")).setSortable(false);
        servicesGrid.addColumn(entry -> {
            Service service = entry.getService();
            if (service.getCalculationBasis() != null) {
                return switch (service.getCalculationBasis()) {
                case DISTANCE -> getTranslation("addjob.services.basis.distance");
                case TIME -> getTranslation("addjob.services.basis.time");
                case FLAT_RATE -> getTranslation("addjob.services.basis.flatrate");
                };
            }
            return "";
        }).setHeader(getTranslation("addjob.services.calculation")).setSortable(true);
        servicesGrid.addColumn(entry -> {
            Service service = entry.getService();
            Double routeDistance = getEffectiveRouteDistance(entry);
            Integer durationSeconds = getEffectiveRouteDuration(entry);
            BigDecimal price = calculateServicePrice(service, routeDistance, durationSeconds);
            if (price.compareTo(BigDecimal.ZERO) > 0) {
                return price.setScale(2, RoundingMode.HALF_UP) + " €";
            }
            if (service.getCalculationBasis() == Service.CalculationBasis.DISTANCE && routeDistance == null) {
                return service.getPricePerKilometer().setScale(2, RoundingMode.HALF_UP) + " €/km ("
                        + getTranslation("addjob.services.route.missing") + ")";
            }
            if (service.getCalculationBasis() == Service.CalculationBasis.TIME && durationSeconds == null) {
                return service.getPricePer15Minutes().setScale(2, RoundingMode.HALF_UP) + " €/15 Min. ("
                        + getTranslation("addjob.services.route.missing") + ")";
            }
            return service.getEffectivePrice() != null
                    ? service.getEffectivePrice().setScale(2, RoundingMode.HALF_UP) + " €"
                    : "";
        }).setHeader(getTranslation("common.price")).setSortable(false);
        servicesGrid.addComponentColumn(entry -> {
            Service service = entry.getService();
            // Verbindliche Leistungen können nicht gelöscht werden
            if (service.isMandatory()) {
                return new Span(""); // Leeres Element statt Löschen-Button
            }
            Button removeButton = new Button(new Icon(VaadinIcon.TRASH));
            removeButton.addThemeVariants(ButtonVariant.LUMO_ERROR, ButtonVariant.LUMO_TERTIARY,
                    ButtonVariant.LUMO_SMALL);
            removeButton.addClickListener(e -> {
                selectedServices.remove(entry);
                servicesGrid.getDataProvider().refreshAll();
                updatePriceSummary();
                triggerValidation();
            });
            return removeButton;
        }).setHeader(getTranslation("common.actions")).setAutoWidth(true).setFlexGrow(0);

        Div servicesGridPanel = new Div(servicesGrid);
        servicesGridPanel.addClassNames("surface-panel", "data-grid-panel");
        servicesGridPanel.setWidthFull();

        priceAndDetailsSection.add(servicesGridPanel);

        // Add Service Button
        Button addServiceButton = new Button(getTranslation("addjob.services.add"), new Icon(VaadinIcon.PLUS));
        addServiceButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
        addServiceButton.addClickListener(e -> openAddServiceDialog());
        priceAndDetailsSection.add(addServiceButton);

        // Price Summary
        VerticalLayout summaryLayout = new VerticalLayout();
        summaryLayout.setPadding(true);
        summaryLayout.setSpacing(true);
        summaryLayout.getStyle().set("border", "1px solid var(--lumo-contrast-20pct)");
        summaryLayout.getStyle().set("border-radius", "var(--lumo-border-radius-m)");
        summaryLayout.getStyle().set("background-color", "var(--lumo-contrast-5pct)");
        summaryLayout.setWidthFull();
        summaryLayout.setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.STRETCH);
        summaryLayout.addClassName("summary-card");

        H3 summaryTitle = new H3(getTranslation("addjob.summary.title"));
        summaryTitle.getStyle().set("margin", "0");
        summaryLayout.add(summaryTitle);

        Div priceTable = new Div();
        priceTable.getStyle().set("width", "100%");

        // Net total row
        Div netRow = new Div();
        netRow.getStyle().set("display", "flex").set("justify-content", "space-between").set("padding", "4px 0");
        Span netLabelSpan = new Span(getTranslation("addjob.summary.net") + ":");
        netLabelSpan.getStyle().set("padding-right", "8px");
        netTotalLabel = new Span("0,00 €");
        netTotalLabel.getStyle().set("font-weight", "bold").set("white-space", "nowrap");
        netRow.add(netLabelSpan, netTotalLabel);
        priceTable.add(netRow);

        // Gross total row
        Div grossRow = new Div();
        grossRow.getStyle().set("display", "flex").set("justify-content", "space-between").set("padding", "4px 0");
        Span grossLabelSpan = new Span(getTranslation("addjob.summary.gross") + ":");
        grossLabelSpan.getStyle().set("padding-right", "8px").set("font-weight", "bold");
        grossTotalLabel = new Span("0,00 €");
        grossTotalLabel.getStyle().set("font-size", "var(--lumo-font-size-l)");
        grossTotalLabel.getStyle().set("font-weight", "bold");
        grossTotalLabel.getStyle().set("color", "var(--lumo-primary-text-color)").set("white-space", "nowrap");
        grossRow.add(grossLabelSpan, grossTotalLabel);
        priceTable.add(grossRow);

        summaryLayout.add(priceTable);

        priceAndDetailsSection.add(summaryLayout);

        // Bemerkung
        H3 remarksTitle = new H3(getTranslation("addjob.tasks.remark"));
        remarksTitle.getStyle().set("margin", "0");
        remarkArea = new TextArea();
        remarkArea.setPlaceholder(getTranslation("addjob.tasks.remark.placeholder"));
        remarkArea.setWidthFull();
        remarkArea.setMinHeight("180px");
        priceAndDetailsSection.add(remarksTitle, remarkArea);

        tabContent.add(priceAndDetailsSection);

        return tabContent;
    }

    private Div createAddStationButton() {
        Div button = new Div();
        button.addClassName("add-station-tile");
        button.getStyle().set("border", "2px dashed var(--lumo-contrast-30pct)");
        button.getStyle().set("border-radius", "var(--lumo-border-radius-m)");
        button.getStyle().set("display", "flex");
        button.getStyle().set("align-items", "center");
        button.getStyle().set("justify-content", "center");
        button.getStyle().set("cursor", "pointer");
        button.getStyle().set("background-color", "var(--lumo-contrast-5pct)");
        button.getStyle().set("aspect-ratio", "1 / 1");
        button.getStyle().set("overflow", "hidden");

        Icon plusIcon = new Icon(VaadinIcon.PLUS);
        plusIcon.setSize("64px");
        plusIcon.getStyle().set("color", "var(--lumo-contrast-40pct)");
        button.add(plusIcon);

        button.addClickListener(e -> {
            if (deliveryStationTilesList.size() >= MAX_DELIVERY_STATIONS) {
                Notification.show(getTranslation("addjob.station.max.reached"), 3000,
                        Notification.Position.BOTTOM_CENTER);
                return;
            }
            addDeliveryStationTile();
        });

        return button;
    }

    private Div createStationSlot(Component content, Span distanceChip) {
        Div slot = new Div();
        slot.addClassName("station-slot");
        slot.getStyle().set("display", "flex");
        slot.getStyle().set("flex-direction", "column");
        slot.getStyle().set("align-items", "center");
        slot.getStyle().set("gap", "var(--lumo-space-s)");
        slot.setWidthFull();

        content.getElement().getClassList().add("station-slot-content");
        content.getElement().getStyle().set("width", "100%");
        slot.add(content);

        if (distanceChip != null) {
            slot.add(distanceChip);
        }

        return slot;
    }

    private Span createDeliveryDistanceChip() {
        Span chip = new Span();
        chip.getStyle().set("display", "inline-flex");
        chip.getStyle().set("align-items", "center");
        chip.getStyle().set("justify-content", "center");
        chip.getStyle().set("padding", "0.35rem 0.75rem");
        chip.getStyle().set("border-radius", "999px");
        chip.getStyle().set("background-color", "var(--lumo-primary-color-10pct)");
        chip.getStyle().set("border", "1px solid var(--lumo-primary-color-30pct)");
        chip.getStyle().set("color", "var(--lumo-primary-text-color)");
        chip.getStyle().set("font-size", "var(--lumo-font-size-s)");
        chip.getStyle().set("font-weight", "600");
        chip.getStyle().set("text-align", "center");
        chip.setVisible(false);
        return chip;
    }

    private void addDeliveryStationTile() {
        int stationNumber = deliveryStationTilesList.size() + 1;
        boolean removable = deliveryStationTilesList.size() > 0; // First station is not removable

        String titleText = getTranslation("addjob.station.delivery", stationNumber);
        StationTile tile = new StationTile(StationTile.StationType.DELIVERY, stationNumber, titleText, removable);

        // Add empty state for this station
        deliveryStationsState.add(new DeliveryStation());
        deliveryStationsSaveAddress.add(true);
        deliveryStationsCustomerId.add(null);
        deliveryStationsAddressDiffers.add(false);
        deliveryStationsMailState.add(null);
        deliveryStationsValidatedByGoogle.add(false);

        int stationIndex = deliveryStationTilesList.size();
        tile.setClickListener(t -> openDeliveryDialog(t, stationIndex));
        tile.setDeleteListener(this::removeDeliveryStationTile);

        Span distanceChip = createDeliveryDistanceChip();
        Div stationSlot = createStationSlot(tile, distanceChip);

        deliveryStationTilesList.add(tile);
        deliveryStationSlotList.add(stationSlot);
        deliveryStationDistanceChips.add(distanceChip);

        // Rebuild grid: remove plus button, add tile, re-add plus button
        stationsGridContainer.remove(addStationButtonSlot);
        stationsGridContainer.add(stationSlot);

        // Hide "+" button if max reached
        if (deliveryStationTilesList.size() < MAX_DELIVERY_STATIONS) {
            stationsGridContainer.add(addStationButtonSlot);
        }

        resetRouteInformation();
        resetStationsAppliedState();
        triggerValidation();
        updateTabLabels();
    }

    private void removeDeliveryStationTile(StationTile tile) {
        int idx = deliveryStationTilesList.indexOf(tile);
        if (idx < 0)
            return;

        Dialog dialog = DialogStylingHelper.createConfirmationDialog(
                getTranslation("addjob.station.remove.confirm", idx + 1),
                null,
                "460px",
                getTranslation("dialog.cancel"),
                getTranslation("dialog.confirm"),
                () -> {
                    int removeIdx = deliveryStationTilesList.indexOf(tile);
                    if (removeIdx < 0)
                        return;

                    deliveryStationTilesList.remove(removeIdx);
                    deliveryStationsState.remove(removeIdx);
                    deliveryStationsSaveAddress.remove(removeIdx);
                    deliveryStationsCustomerId.remove(removeIdx);
                    deliveryStationsAddressDiffers.remove(removeIdx);
                    deliveryStationsMailState.remove(removeIdx);
                    deliveryStationsValidatedByGoogle.remove(removeIdx);
                    deliveryStationTasksState.remove(removeIdx);
                    Div removedSlot = deliveryStationSlotList.remove(removeIdx);
                    deliveryStationDistanceChips.remove(removeIdx);
                    pickupToDeliveryRouteResults.remove(removeIdx);
                    // Re-index tasks state for remaining stations
                    Map<Integer, List<BaseTask>> reindexed = new HashMap<>();
                    for (Map.Entry<Integer, List<BaseTask>> entry : deliveryStationTasksState.entrySet()) {
                        int oldIdx = entry.getKey();
                        int newIdx = oldIdx > removeIdx ? oldIdx - 1 : oldIdx;
                        reindexed.put(newIdx, entry.getValue());
                    }
                    deliveryStationTasksState.clear();
                    deliveryStationTasksState.putAll(reindexed);

                    Map<Integer, RouteCalculationResult> reindexedRoutes = new HashMap<>();
                    for (Map.Entry<Integer, RouteCalculationResult> entry : pickupToDeliveryRouteResults.entrySet()) {
                        int oldIdx = entry.getKey();
                        int newIdx = oldIdx > removeIdx ? oldIdx - 1 : oldIdx;
                        reindexedRoutes.put(newIdx, entry.getValue());
                    }
                    pickupToDeliveryRouteResults.clear();
                    pickupToDeliveryRouteResults.putAll(reindexedRoutes);

                    for (SelectedServiceEntry selectedService : selectedServices) {
                        Integer stationOrder = selectedService.getDeliveryStationOrder();
                        if (stationOrder == null) {
                            continue;
                        }
                        if (stationOrder == removeIdx) {
                            selectedService.setDeliveryStationOrder(deliveryStationsState.isEmpty() ? null : 0);
                        } else if (stationOrder > removeIdx) {
                            selectedService.setDeliveryStationOrder(stationOrder - 1);
                        }
                    }
                    stationsGridContainer.remove(removedSlot);

                    // Renumber remaining tiles and update click listeners
                    for (int i = 0; i < deliveryStationTilesList.size(); i++) {
                        StationTile t = deliveryStationTilesList.get(i);
                        int newNumber = i + 1;
                        t.updateStationNumber(newNumber);
                        t.updateTitle(getTranslation("addjob.station.delivery", newNumber));
                        // Update click listener to use correct index
                        final int newIdx = i;
                        t.setClickListener(tt -> openDeliveryDialog(tt, newIdx));
                        // First station should not be removable
                        if (i == 0) {
                            t.setDeleteListener(null);
                        }
                    }

                    // Ensure "+" button is visible if under max
                    if (deliveryStationTilesList.size() < MAX_DELIVERY_STATIONS
                            && addStationButtonSlot.getParent().isEmpty()) {
                        stationsGridContainer.add(addStationButtonSlot);
                    }

                    resetRouteInformation();
                    resetStationsAppliedState();
                    if (servicesGrid != null) {
                        servicesGrid.getDataProvider().refreshAll();
                    }
                    updatePriceSummary();
                    triggerValidation();
                    updateTabLabels();
                },
                ButtonVariant.LUMO_PRIMARY);
        dialog.open();
    }

    private void openPickupDialog() {
        List<Customer> customers = customerService.findAllForCurrentOwner();
        DeliveryStationTile.TranslationHelper translationHelper = this::getTranslation;

        PickupStationDialog dialog = new PickupStationDialog(getTranslation("addjob.section.pickup"), customers,
                translationHelper, data -> {
                    // Update customer selection from dialog
                    if (data.getCustomerSelection() != null) {
                        setCustomerSelectionValue(data.getCustomerSelection());
                    } else {
                        customerSelection.clear();
                    }

                    // Update pickup fields from dialog data
                    pickupCompany.setValue(data.getCompany() != null ? data.getCompany() : "");
                    pickupSalutation.setValue(data.getSalutation() != null ? data.getSalutation() : "");
                    pickupFirstName.setValue(data.getFirstName() != null ? data.getFirstName() : "");
                    pickupLastName.setValue(data.getLastName() != null ? data.getLastName() : "");
                    pickupPhone.setValue(data.getPhone() != null ? data.getPhone() : "");
                    pickupMail = trimToNull(data.getMail());
                    pickupStreet.setValue(data.getStreet() != null ? data.getStreet() : "");
                    pickupHouseNumber.setValue(data.getHouseNumber() != null ? data.getHouseNumber() : "");
                    pickupAddressAddition.setValue(data.getAddressAddition() != null ? data.getAddressAddition() : "");
                    pickupZip.setValue(data.getZip() != null ? data.getZip() : "");
                    pickupCity.setValue(data.getCity() != null ? data.getCity() : "");
                    savePickupAddress.setValue(data.isSaveAddress());
                    pickupCustomerId = data.getCustomerId();
                    pickupAddressDiffers = data.isAddressDiffersFromCustomer();

                    // Sync appointment fields for binder/submit
                    pickupDate.setValue(data.getAppointmentDate());
                    pickupTime.setValue(data.getAppointmentTime());
                    digitalProcessing.setValue(data.isDigitalProcessing());
                    if (data.getAppUser() != null) {
                        appUser.setValue(data.getAppUser());
                    } else {
                        appUser.clear();
                    }

                    // Sync cargo items
                    cargoItemsState.clear();
                    if (data.getCargoItems() != null) {
                        cargoItemsState.addAll(data.getCargoItems());
                    }

                    // Update tile preview
                    pickupTile.updatePreview(data.getCompany(), data.getFirstName(), data.getLastName(),
                            data.getStreet(), data.getHouseNumber(), data.getZip(), data.getCity(),
                            buildPickupPreviewDetails(data.getAppointmentDate(), data.getAppointmentTime(),
                                    data.isDigitalProcessing(), data.getAppUser(), data.getCargoItems()));
                    pickupAddressValidatedByGoogle = data.isAddressValidatedByGoogle();
                    storePickupAddressValidationResult(data.getAddressValidationResult());
                    pickupTile.setAddressValidated(pickupAddressValidatedByGoogle);

                    resetRouteInformation();
                    resetStationsAppliedState();
                    triggerValidation();
                    updateTabLabels();
                }, availableAppUsers, addressValidationService);

        // Pre-fill dialog with current pickup field values
        PickupStationDialog.PickupData currentData = new PickupStationDialog.PickupData();
        currentData.setCompany(pickupCompany.getValue());
        currentData.setSalutation(pickupSalutation.getValue());
        currentData.setFirstName(pickupFirstName.getValue());
        currentData.setLastName(pickupLastName.getValue());
        currentData.setPhone(pickupPhone.getValue());
        currentData.setMail(pickupMail);
        currentData.setStreet(pickupStreet.getValue());
        currentData.setHouseNumber(pickupHouseNumber.getValue());
        currentData.setAddressAddition(pickupAddressAddition.getValue());
        currentData.setZip(pickupZip.getValue());
        currentData.setCity(pickupCity.getValue());
        currentData.setSaveAddress(savePickupAddress.getValue());
        currentData.setCustomerId(pickupCustomerId);
        currentData.setCustomerSelection(customerSelection.getValue());
        // Pre-fill pickup-specific fields
        currentData.setAppointmentDate(pickupDate.getValue());
        currentData.setAppointmentTime(pickupTime != null ? pickupTime.getValue() : null);
        currentData.setDigitalProcessing(digitalProcessing.getValue());
        currentData.setAppUser(appUser.getValue());
        currentData.setCargoItems(new ArrayList<>(cargoItemsState));
        currentData.setAddressValidatedByGoogle(pickupAddressValidatedByGoogle);
        dialog.setData(currentData);

        dialog.open();
    }

    private List<String> buildPickupPreviewDetails(LocalDate appointmentDate, LocalTime appointmentTime,
            boolean digitalProcessingEnabled, AppUser assignedAppUser, List<CargoItem> cargoItems) {
        List<String> previewDetails = new ArrayList<>();

        previewDetails.add(getTranslation("addjob.tab.cargo") + ": " + summarizeCargoItems(cargoItems));

        String appointmentPreview = formatPickupAppointment(appointmentDate, appointmentTime);
        if (appointmentPreview != null) {
            previewDetails.add(getTranslation("addjob.appointment.pickup") + ": " + appointmentPreview);
        }

        previewDetails.add(buildDigitalProcessingPreview(digitalProcessingEnabled, assignedAppUser));
        return previewDetails;
    }

    private String summarizeCargoItems(List<CargoItem> cargoItems) {
        if (cargoItems == null || cargoItems.isEmpty()) {
            return getTranslation("jobsummary.cargo.none");
        }

        List<String> summaries = new ArrayList<>();
        for (CargoItem cargoItem : cargoItems) {
            if (cargoItem == null) {
                continue;
            }

            String description = cargoItem.getDescription() != null ? cargoItem.getDescription().trim() : "";
            Integer quantity = cargoItem.getQuantity();
            if (description.isEmpty() && quantity == null) {
                continue;
            }

            StringBuilder summary = new StringBuilder();
            if (quantity != null) {
                summary.append(quantity).append("x ");
            }
            summary.append(description.isEmpty() ? getTranslation("addjob.tab.cargo") : description);
            summaries.add(summary.toString().trim());
        }

        if (summaries.isEmpty()) {
            return getTranslation("jobsummary.cargo.none");
        }
        if (summaries.size() <= 2) {
            return String.join(", ", summaries);
        }
        return String.join(", ", summaries.subList(0, 2)) + " +" + (summaries.size() - 2);
    }

    private String formatPickupAppointment(LocalDate appointmentDate, LocalTime appointmentTime) {
        if (appointmentDate == null) {
            return null;
        }

        String formattedDate = appointmentDate.format(PICKUP_PREVIEW_DATE_FORMATTER);
        if (appointmentTime == null) {
            return formattedDate;
        }
        return formattedDate + " " + appointmentTime.format(PICKUP_PREVIEW_TIME_FORMATTER);
    }

    private String buildDigitalProcessingPreview(boolean digitalProcessingEnabled, AppUser assignedAppUser) {
        StringBuilder preview = new StringBuilder();
        preview.append(getTranslation("profile.settings.digitalprocess")).append(": ")
                .append(getTranslation(digitalProcessingEnabled ? "common.yes" : "common.no"));

        String appUserLabel = formatAssignedAppUser(assignedAppUser);
        if (digitalProcessingEnabled && appUserLabel != null) {
            preview.append(" (").append(appUserLabel).append(")");
        }
        return preview.toString();
    }

    private String formatAssignedAppUser(AppUser assignedAppUser) {
        if (assignedAppUser == null) {
            return null;
        }

        String fullName = ((assignedAppUser.getVorname() != null ? assignedAppUser.getVorname() : "") + " "
                + (assignedAppUser.getNachname() != null ? assignedAppUser.getNachname() : "")).trim();
        if (!fullName.isEmpty()) {
            return fullName;
        }
        if (assignedAppUser.getBezeichnung() != null && !assignedAppUser.getBezeichnung().isBlank()) {
            return assignedAppUser.getBezeichnung().trim();
        }
        if (assignedAppUser.getEmail() != null && !assignedAppUser.getEmail().isBlank()) {
            return assignedAppUser.getEmail().trim();
        }
        return null;
    }

    private List<String> buildDeliveryPreviewDetails(List<BaseTask> tasks) {
        if (tasks == null || tasks.isEmpty()) {
            return List.of(getTranslation("addjob.tab.tasks") + ": " + getTranslation("jobsummary.tasks.none"));
        }

        List<String> summaries = new ArrayList<>();
        for (BaseTask task : tasks) {
            if (task != null) {
                summaries.add(summarizeDeliveryTask(task));
            }
        }

        if (summaries.isEmpty()) {
            return List.of(getTranslation("addjob.tab.tasks") + ": " + getTranslation("jobsummary.tasks.none"));
        }

        return summaries;
    }

    private String summarizeDeliveryTask(BaseTask task) {
        if (task instanceof ConfirmationTask confirmationTask) {
            String buttonText = trimToNull(confirmationTask.getButtonText());
            if (buttonText != null) {
                return confirmationTask.getDisplayName() + " \"" + buttonText + "\"";
            }

            String description = trimToNull(confirmationTask.getDescription());
            return description != null ? confirmationTask.getDisplayName() + " \"" + description + "\""
                    : confirmationTask.getDisplayName();
        }

        if (task instanceof TodoListTask todoListTask) {
            long itemCount = todoListTask.getTodoItems() == null ? 0
                    : todoListTask.getTodoItems().stream().filter(Objects::nonNull).map(String::trim)
                            .filter(item -> !item.isEmpty()).count();
            return itemCount > 0 ? task.getDisplayName() + " (" + itemCount + ")" : task.getDisplayName();
        }

        if (task instanceof PhotoTask photoTask) {
            String range = formatMinMaxRange(photoTask.getMinPhotoCount(), photoTask.getMaxPhotoCount());
            return range.isBlank() ? task.getDisplayName() : task.getDisplayName() + " " + range;
        }

        if (task instanceof BarcodeTask barcodeTask) {
            String range = formatMinMaxRange(barcodeTask.getMinBarcodeCount(), barcodeTask.getMaxBarcodeCount());
            return range.isBlank() ? task.getDisplayName() : task.getDisplayName() + " " + range;
        }

        if (task instanceof CommentTask commentTask) {
            String commentText = trimToNull(commentTask.getCommentText());
            if (commentText != null) {
                return task.getDisplayName() + " \"" + commentText + "\"";
            }
            return commentTask.isRequired() ? task.getDisplayName() + " (" + getTranslation("common.required") + ")"
                    : task.getDisplayName();
        }

        if (task instanceof SignatureTask) {
            return task.getDisplayName();
        }

        String description = trimToNull(task.getDescription());
        return description != null ? task.getDisplayName() + " \"" + description + "\"" : task.getDisplayName();
    }

    private String formatMinMaxRange(Integer minValue, Integer maxValue) {
        if (minValue != null && maxValue != null) {
            return minValue.equals(maxValue) ? String.valueOf(minValue) : minValue + "-" + maxValue;
        }
        if (minValue != null) {
            return String.valueOf(minValue);
        }
        if (maxValue != null) {
            return String.valueOf(maxValue);
        }
        return "";
    }

    private String trimToNull(String value) {
        if (value == null) {
            return null;
        }

        String trimmed = value.trim();
        return trimmed.isEmpty() ? null : trimmed;
    }

    private void setCustomerSelectionValue(String value) {
        String normalizedValue = trimToNull(value);
        if (normalizedValue == null) {
            customerSelection.clear();
            return;
        }
        if (!customerLabelToEntity.containsKey(normalizedValue)) {
            customerLabelToEntity.put(normalizedValue, null);
            customerSelection.setItems(new ArrayList<>(customerLabelToEntity.keySet()));
        }
        customerSelection.setValue(normalizedValue);
    }

    private void openDeliveryDialog(StationTile tile, int stationIndex) {
        // Ensure index is valid (could have changed due to deletions)
        int actualIndex = deliveryStationTilesList.indexOf(tile);
        if (actualIndex < 0 || actualIndex >= deliveryStationsState.size())
            return;

        List<Customer> customers = customerService.findAllForCurrentOwner();
        DeliveryStationTile.TranslationHelper translationHelper = this::getTranslation;

        // Load task templates for the current user
        List<TaskTemplate> templates = loadTaskTemplates();

        DeliveryStationDialog dialog = new DeliveryStationDialog(
                getTranslation("addjob.station.delivery", actualIndex + 1), customers, translationHelper, data -> {
                    int idx = deliveryStationTilesList.indexOf(tile);
                    if (idx < 0 || idx >= deliveryStationsState.size())
                        return;

                    // Update state
                    DeliveryStation station = deliveryStationsState.get(idx);
                    station.setCompany(data.getCompany());
                    station.setSalutation(data.getSalutation());
                    station.setFirstName(data.getFirstName());
                    station.setLastName(data.getLastName());
                    station.setPhone(data.getPhone());
                    station.setStreet(data.getStreet());
                    station.setHouseNumber(data.getHouseNumber());
                    station.setAddressAddition(data.getAddressAddition());
                    station.setZip(data.getZip());
                    station.setCity(data.getCity());
                    station.setTasks(data.getTasks() != null ? new ArrayList<>(data.getTasks()) : new ArrayList<>());
                    deliveryStationsSaveAddress.set(idx, data.isSaveAddress());
                    while (deliveryStationsCustomerId.size() <= idx) {
                        deliveryStationsCustomerId.add(null);
                    }
                    deliveryStationsCustomerId.set(idx, data.getCustomerId());
                    while (deliveryStationsAddressDiffers.size() <= idx) {
                        deliveryStationsAddressDiffers.add(false);
                    }
                    deliveryStationsAddressDiffers.set(idx, data.isAddressDiffersFromCustomer());
                    deliveryStationsMailState.set(idx, trimToNull(data.getMail()));

                    // Store tasks for this delivery station
                    deliveryStationTasksState.put(idx, data.getTasks() != null ? data.getTasks() : new ArrayList<>());

                    // Update tile preview
                    tile.updatePreview(data.getCompany(), data.getFirstName(), data.getLastName(), data.getStreet(),
                            data.getHouseNumber(), data.getZip(), data.getCity(),
                            buildDeliveryPreviewDetails(data.getTasks()));
                    deliveryStationsValidatedByGoogle.set(idx, data.isAddressValidatedByGoogle());
                    storeDeliveryAddressValidationResult(idx, data.getAddressValidationResult());
                    tile.setAddressValidated(data.isAddressValidatedByGoogle());

                    resetRouteInformation();
                    resetStationsAppliedState();
                    triggerValidation();
                    updateTabLabels();
                }, templates, (templateName, tasks) -> {
                    try {
                        taskTemplateService.createTemplate(securityService.getCurrentDatabaseUser().getId(),
                                templateName, tasks);
                        Notification.show(getTranslation("addjob.tasks.template.saved", templateName), 3000,
                                Notification.Position.BOTTOM_END);
                    } catch (Exception ex) {
                        log.error("Error saving task template", ex);
                        Notification.show(getTranslation("addjob.tasks.template.save.error", ex.getMessage()), 4000,
                                Notification.Position.MIDDLE);
                    }
                }, addressValidationService);

        // Pre-fill dialog with current station data
        DeliveryStation station = deliveryStationsState.get(actualIndex);
        DeliveryStationDialog.DeliveryData currentData = new DeliveryStationDialog.DeliveryData();
        currentData.setCompany(station.getCompany());
        currentData.setSalutation(station.getSalutation());
        currentData.setFirstName(station.getFirstName());
        currentData.setLastName(station.getLastName());
        currentData.setPhone(station.getPhone());
        currentData.setMail(actualIndex < deliveryStationsMailState.size() ? deliveryStationsMailState.get(actualIndex) : null);
        currentData.setStreet(station.getStreet());
        currentData.setHouseNumber(station.getHouseNumber());
        currentData.setAddressAddition(station.getAddressAddition());
        currentData.setZip(station.getZip());
        currentData.setCity(station.getCity());
        currentData.setSaveAddress(deliveryStationsSaveAddress.get(actualIndex));
        if (actualIndex < deliveryStationsCustomerId.size()) {
            currentData.setCustomerId(deliveryStationsCustomerId.get(actualIndex));
        }
        if (actualIndex < deliveryStationsValidatedByGoogle.size()) {
            currentData.setAddressValidatedByGoogle(deliveryStationsValidatedByGoogle.get(actualIndex));
        }
        // Load existing tasks for this station
        List<BaseTask> stationTasks = deliveryStationTasksState.get(actualIndex);
        if (stationTasks != null) {
            currentData.setTasks(stationTasks);
        }
        dialog.setData(currentData);

        dialog.open();
    }

    private void openAddServiceDialog() {
        Dialog dialog = DialogStylingHelper.createStyledDialog(getTranslation("addjob.services.dialog.title"), "720px");
        dialog.setCloseOnOutsideClick(false);

        VerticalLayout dialogContent = DialogStylingHelper.createContentLayout("620px");

        // Load available services for current user
        List<Service> availableServices = serviceRepository
                .findByUserId(securityService.getCurrentDatabaseUser().getId().toString());

        ComboBox<Service> serviceCombo = new ComboBox<>(getTranslation("common.service"));
        serviceCombo.setWidthFull();
        serviceCombo.setItems(availableServices);
        serviceCombo.setItemLabelGenerator(service -> {
            // Only show price for FLAT_RATE services
            if (service.getCalculationBasis() == Service.CalculationBasis.FLAT_RATE
                    && service.getEffectivePrice() != null) {
                return service.getName() + " (" + service.getEffectivePrice().setScale(2, RoundingMode.HALF_UP) + " €)";
            }
            return service.getName();
        });
        serviceCombo.setPlaceholder(getTranslation("addjob.services.dialog.placeholder"));
        serviceCombo.setRequired(true);

        ComboBox<Integer> deliveryStationCombo = new ComboBox<>(getTranslation("addjob.services.deliverystation"));
        deliveryStationCombo.setWidthFull();
        deliveryStationCombo.setRequired(true);
        deliveryStationCombo.setRequiredIndicatorVisible(true);
        deliveryStationCombo.setItems(getAvailableDeliveryStationOrders());
        deliveryStationCombo.setItemLabelGenerator(this::buildDeliveryStationSelectionLabel);
        deliveryStationCombo.setPlaceholder(getTranslation("addjob.services.dialog.station.placeholder"));
        if (!deliveryStationsState.isEmpty()) {
            deliveryStationCombo.setValue(0);
        }

        dialogContent.add(serviceCombo, deliveryStationCombo);

        Button cancelButton = new Button(getTranslation("button.cancel"), e -> dialog.close());
        cancelButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);

        Button addButton = new Button(getTranslation("addjob.services.dialog.add"), e -> {
            if (serviceCombo.getValue() != null && deliveryStationCombo.getValue() != null) {
                selectedServices
                        .add(new SelectedServiceEntry(serviceCombo.getValue(), deliveryStationCombo.getValue()));
                servicesGrid.getDataProvider().refreshAll();
                updatePriceSummary();
                triggerValidation();
                updateTabLabels();
                dialog.close();
            }
        });
        addButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);

        dialog.add(DialogStylingHelper.wrapContent(dialogContent));
        dialog.getFooter().add(cancelButton, addButton);
        dialog.open();
    }

    private void updatePriceSummary() {
        BigDecimal netTotal = BigDecimal.ZERO;
        BigDecimal grossTotal = BigDecimal.ZERO;

        for (SelectedServiceEntry entry : selectedServices) {
            Service service = entry.getService();
            BigDecimal price = calculateServicePrice(service, getEffectiveRouteDistance(entry),
                    getEffectiveRouteDuration(entry));
            BigDecimal vatRate = Service.FIXED_VAT_RATE;

            netTotal = netTotal.add(price);
            BigDecimal vatAmount = price.multiply(vatRate);
            grossTotal = grossTotal.add(price.add(vatAmount));
        }

        netTotalLabel.setText(netTotal.setScale(2, RoundingMode.HALF_UP).toString().replace(".", ",") + " €");
        grossTotalLabel.setText(grossTotal.setScale(2, RoundingMode.HALF_UP).toString().replace(".", ",") + " €");
    }

    private List<Integer> getAvailableDeliveryStationOrders() {
        List<Integer> stationOrders = new ArrayList<>();
        for (int i = 0; i < deliveryStationsState.size(); i++) {
            stationOrders.add(i);
        }
        return stationOrders;
    }

    private String formatSelectedServiceStationLabel(SelectedServiceEntry entry) {
        return formatDeliveryStationLabel(entry.getDeliveryStationOrder());
    }

    private String buildDeliveryStationSelectionLabel(Integer stationOrder) {
        if (stationOrder == null || stationOrder < 0 || stationOrder >= deliveryStationsState.size()) {
            return "-";
        }

        DeliveryStation station = deliveryStationsState.get(stationOrder);
        StringBuilder label = new StringBuilder(getTranslation("addjob.station.delivery", stationOrder + 1));

        String city = trimToNull(station.getCity());
        if (city != null) {
            label.append(" - ").append(city);
        } else {
            String company = trimToNull(station.getCompany());
            if (company != null) {
                label.append(" - ").append(company);
            }
        }

        return label.toString();
    }

    private String formatDeliveryStationLabel(Integer stationOrder) {
        if (stationOrder == null || stationOrder < 0) {
            return "-";
        }
        return "Lieferstation " + (stationOrder + 1);
    }

    private BigDecimal calculateServicePrice(Service service, Double routeDistance, Integer durationSeconds) {
        if (service.getCalculationBasis() == null) {
            return BigDecimal.ZERO;
        }

        switch (service.getCalculationBasis()) {
        case FLAT_RATE:
            return service.getPrice() != null ? service.getPrice() : BigDecimal.ZERO;

        case DISTANCE:
            if (service.getPricePerKilometer() != null && routeDistance != null && routeDistance > 0) {
                return service.getPricePerKilometer().multiply(BigDecimal.valueOf(routeDistance));
            }
            return BigDecimal.ZERO;

        case TIME:
            if (service.getPricePer15Minutes() != null && durationSeconds != null && durationSeconds > 0) {
                // Dauer in 15-Minuten-Einheiten umrechnen (aufrunden)
                int units = durationSeconds / 900; // 900 Sekunden = 15 Minuten
                if (durationSeconds % 900 > 0)
                    units++; // Aufrunden
                return service.getPricePer15Minutes().multiply(BigDecimal.valueOf(units));
            }
            return BigDecimal.ZERO;

        default:
            return BigDecimal.ZERO;
        }
    }

    private Double getEffectiveRouteDistance(SelectedServiceEntry entry) {
        RouteCalculationResult stationRoute = getSelectedServiceRoute(entry);
        if (stationRoute != null && stationRoute.isValid()) {
            return stationRoute.getDistanceKm();
        }
        if (routeCalculationResult == null || !routeCalculationResult.isValid()) {
            return getEffectiveRouteDistance();
        }
        return null;
    }

    private Integer getEffectiveRouteDuration(SelectedServiceEntry entry) {
        RouteCalculationResult stationRoute = getSelectedServiceRoute(entry);
        if (stationRoute != null && stationRoute.isValid()) {
            return stationRoute.getDurationSeconds();
        }
        if (routeCalculationResult == null || !routeCalculationResult.isValid()) {
            return getEffectiveRouteDuration();
        }
        return null;
    }

    private RouteCalculationResult getSelectedServiceRoute(SelectedServiceEntry entry) {
        if (entry == null || entry.getDeliveryStationOrder() == null) {
            return null;
        }
        return pickupToDeliveryRouteResults.get(entry.getDeliveryStationOrder());
    }

    private JobServiceSelection toJobServiceSelection(SelectedServiceEntry entry) {
        JobServiceSelection selection = new JobServiceSelection();
        selection.setServiceId(entry.getService().getId());
        selection.setDeliveryStationOrder(entry.getDeliveryStationOrder());
        selection.setRouteDistanceKm(getEffectiveRouteDistance(entry));
        selection.setRouteDurationSeconds(getEffectiveRouteDuration(entry));
        return selection;
    }

    // createPickupSection(), togglePickupCollapse(), updateAddStationButtonSize()
    // removed - replaced by StationTile + StationDialog

    // createDeliverySection() removed - delivery stations are now handled by
    // DeliveryStationTile

    private void setupCompanyAutocomplete(ComboBox<String> companyField, boolean isPickup) {
        // Get all customers for the current owner
        List<Customer> allCustomers = customerService.findAllForCurrentOwner();

        Map<String, Customer> addressOptions = new LinkedHashMap<>();
        for (Customer customer : allCustomers) {
            CustomerAddressLabelHelper.put(addressOptions, customer, getTranslation("addjob.customer.unnamed"));
        }

        // Set items for autocomplete
        companyField.setItems(new ArrayList<>(addressOptions.keySet()));

        // Add selection listener to auto-fill pickup address fields when company is
        // selected
        companyField.addValueChangeListener(event -> {
            String selectedAddress = event.getValue();
            if (selectedAddress == null || selectedAddress.trim().isEmpty()) {
                return;
            }

            // Streckeninformationen zurücksetzen, da sich die Adresse ändert
            resetRouteInformation();

            Customer customer = addressOptions.get(selectedAddress);

            if (customer != null) {
                pickupCustomerId = customer.getId();

                // Fill pickup address fields
                if (customer.getTitle() != null
                        && ("Herr".equalsIgnoreCase(customer.getTitle()) || "Frau".equalsIgnoreCase(customer.getTitle())
                                || "Divers".equalsIgnoreCase(customer.getTitle()))) {
                    pickupSalutation.setValue(customer.getTitle());
                }
                if (customer.getFirstname() != null)
                    pickupFirstName.setValue(customer.getFirstname());
                if (customer.getLastName() != null)
                    pickupLastName.setValue(customer.getLastName());
                if (customer.getTelephone() != null)
                    pickupPhone.setValue(customer.getTelephone());
                pickupMail = trimToNull(customer.getMail());
                if (customer.getStreet() != null)
                    pickupStreet.setValue(customer.getStreet());
                if (customer.getHouseNumber() != null)
                    pickupHouseNumber.setValue(customer.getHouseNumber());
                if (customer.getAddressAddition() != null)
                    pickupAddressAddition.setValue(customer.getAddressAddition());
                if (customer.getZip() != null)
                    pickupZip.setValue(customer.getZip());
                if (customer.getCity() != null)
                    pickupCity.setValue(customer.getCity());

                // Deactivate save checkbox since customer already exists
                savePickupAddress.setValue(false);
            }
        });

        // Handle custom values (when user types something not in the list)
        companyField.addCustomValueSetListener(event -> {
            String customValue = event.getDetail();
            companyField.setValue(customValue);

            // Streckeninformationen zurücksetzen
            resetRouteInformation();

            // Reactivate save checkbox for custom values
            savePickupAddress.setValue(true);
            pickupCustomerId = null;
            pickupMail = null;
        });
    }

    private void setupValidation() {
        // Bind pickup address fields with validation
        binder.forField(pickupFirstName).asRequired("").bind(Job::getPickupFirstName, Job::setPickupFirstName);

        binder.forField(pickupLastName).asRequired("").bind(Job::getPickupLastName, Job::setPickupLastName);

        binder.forField(pickupStreet).asRequired("").bind(Job::getPickupStreet, Job::setPickupStreet);

        binder.forField(pickupHouseNumber).asRequired("").bind(Job::getPickupHouseNumber, Job::setPickupHouseNumber);

        binder.forField(pickupZip).asRequired("").bind(Job::getPickupZip, Job::setPickupZip);

        binder.forField(pickupCity).asRequired("").bind(Job::getPickupCity, Job::setPickupCity);

        // Delivery address fields are managed by DeliveryStationTile (not binder)

        // Price wird manuell in submit() berechnet und gesetzt - kein Binder notwendig

        // Bind pickup date field with validation
        binder.forField(pickupDate).asRequired("")
                .withValidator(date -> date == null || !date.isBefore(LocalDate.now()),
                        getTranslation("addjob.validation.pickupdate.future"))
                .bind(Job::getPickupDate, Job::setPickupDate);

        // Delivery dates are now per-station (in DeliveryStationTile)

        // Bind time picker fields (optional)
        binder.bind(pickupTime, Job::getPickupTime, Job::setPickupTime);

        // Bind customerSelection field with validation
        binder.forField(customerSelection).asRequired("").bind(Job::getCustomerSelection, Job::setCustomerSelection);

        // Bind optional pickup fields without validation
        binder.bind(pickupCompany, Job::getPickupCompany, Job::setPickupCompany);
        binder.bind(pickupSalutation, Job::getPickupSalutation, Job::setPickupSalutation);
        binder.bind(pickupPhone, Job::getPickupPhone, Job::setPickupPhone);
        binder.bind(pickupAddressAddition, Job::getPickupAddressAddition, Job::setPickupAddressAddition);

        binder.forField(digitalProcessing).bind(Job::isDigitalProcessing,
                (job, value) -> job.setDigitalProcessing(Boolean.TRUE.equals(value)));

        // Bind appUser with converter: AppUser object <-> String ID
        binder.forField(appUser).withConverter(
                // Convert AppUser to String (ID)
                user -> user != null ? user.getId().toHexString() : null,
                // Convert String (ID) back to AppUser
                id -> {
                    if (id == null || id.trim().isEmpty())
                        return null;
                    return availableAppUsers.stream().filter(user -> user.getId().toHexString().equals(id)).findFirst()
                            .orElse(null);
                })
                // Require App-Nutzer when digital processing is enabled
                .withValidator(selectedUserId -> {
                    boolean digital = Boolean.TRUE.equals(digitalProcessing.getValue());
                    boolean hasUser = selectedUserId != null && !selectedUserId.trim().isEmpty();
                    return !digital || hasUser;
                }, getTranslation("addjob.validation.appuser.required")).bind(Job::getAppUser, Job::setAppUser);

        // Toggle required indicator and visibility for App-Nutzer based on
        // digitalProcessing
        digitalProcessing.addValueChangeListener(e -> {
            boolean required = Boolean.TRUE.equals(e.getValue());
            appUser.setRequiredIndicatorVisible(required);
            appUser.setVisible(required);

            if (!required) {
                // Clear app user and all station tasks when digital processing is disabled
                appUser.clear();
                deliveryStationTasksState.clear();
            }

            triggerValidation();
            updateTabLabels();
        });
        // Also revalidate when appUser changes
        appUser.addValueChangeListener(e -> {
            triggerValidation();
            updateTabLabels();
        });
        // Initialize required indicator and visibility state
        boolean digitalInitial = Boolean.TRUE.equals(digitalProcessing.getValue());
        appUser.setRequiredIndicatorVisible(digitalInitial);
        appUser.setVisible(digitalInitial);

        // Set up validation triggers and visual styling
        setupValidationTriggers();

        // Trigger initial validation when view is displayed
        triggerValidation();

        // Update tab labels with initial validation state
        updateTabLabels();
    }

    private void setupValidationTriggers() {
        // List of all required fields
        TextField[] requiredTextFields = { pickupFirstName, pickupLastName, pickupStreet, pickupHouseNumber, pickupZip,
                pickupCity };

        // List of required date fields (delivery dates are per-station in tiles)
        DatePicker[] requiredDateFields = { pickupDate };

        // Add validation listener for customerSelection ComboBox
        customerSelection.addValueChangeListener(event -> {
            triggerValidation();
            updateTabLabels();
        });

        // Add value change listeners to trigger validation on every change for text
        // fields
        for (TextField field : requiredTextFields) {
            field.addValueChangeListener(event -> {
                triggerValidation();
                updateFieldStyling(field);
                updateTabLabels();
            });

            // Add focus listeners for immediate visual feedback
            field.addFocusListener(event -> updateFieldStyling(field));
            field.addBlurListener(event -> updateFieldStyling(field));

            // Set initial styling
            updateFieldStyling(field);
        }

        // Add value change listeners for date fields
        for (DatePicker field : requiredDateFields) {
            field.addValueChangeListener(event -> {
                triggerValidation();
                updateDateFieldStyling(field);
                updateTabLabels();
            });

            // Add focus listeners for immediate visual feedback
            field.addFocusListener(event -> updateDateFieldStyling(field));
            field.addBlurListener(event -> updateDateFieldStyling(field));

            // Set initial styling
            updateDateFieldStyling(field);
        }
    }

    private void triggerValidation() {
        // Create a temporary job object to trigger validation
        binder.validate();

        // Update submit button state based on all validation checks
        if (submitButton != null) {
            boolean hasErrors = hasAddressValidationErrors() || hasAppointmentValidationErrors()
                    || hasCargoValidationErrors() || hasPriceValidationErrors() || hasTasksValidationErrors();
            submitButton.setEnabled(!hasErrors);
        }

        // Update "Stationen übernehmen" button state
        updateApplyStationsButtonState();
    }

    private void updateFieldStyling(TextField field) {
        String value = field.getValue();
        boolean isEmpty = value == null || value.trim().isEmpty();

        if (isEmpty) {
            // Apply transparent red background only to the input field, not the label
            field.getStyle().set("--vaadin-input-field-background", "rgba(255, 0, 0, 0.1)");
            field.getStyle().set("--vaadin-input-field-border-color", "rgba(255, 0, 0, 0.3)");
        } else {
            // Remove styling when field is filled
            field.getStyle().remove("--vaadin-input-field-background");
            field.getStyle().remove("--vaadin-input-field-border-color");
        }
    }

    private void updateDateFieldStyling(DatePicker field) {
        boolean isEmpty = field.getValue() == null;

        if (isEmpty) {
            // Apply transparent red background only to the input field, not the label
            field.getStyle().set("--vaadin-input-field-background", "rgba(255, 0, 0, 0.1)");
            field.getStyle().set("--vaadin-input-field-border-color", "rgba(255, 0, 0, 0.3)");
        } else {
            // Remove styling when field is filled
            field.getStyle().remove("--vaadin-input-field-background");
            field.getStyle().remove("--vaadin-input-field-border-color");
        }
    }

    private void updateTabLabels() {
        // No-op: tabs removed, validation is shown via field styling and submit button
        // state
    }

    private boolean hasAddressValidationErrors() {
        // Check customer selection
        boolean customerSelectionEmpty = customerSelection.getValue() == null
                || customerSelection.getValue().trim().isEmpty();

        // Check pickup address fields
        boolean pickupErrors = isFieldEmpty(pickupFirstName) || isFieldEmpty(pickupLastName)
                || isFieldEmpty(pickupStreet) || isFieldEmpty(pickupHouseNumber) || isFieldEmpty(pickupZip)
                || isFieldEmpty(pickupCity);

        // Check all delivery stations for errors (from state)
        boolean deliveryErrors = deliveryStationsState.isEmpty()
                || deliveryStationsState.stream().anyMatch(this::hasDeliveryStationValidationErrors);

        return customerSelectionEmpty || pickupErrors || deliveryErrors;
    }

    private boolean hasDeliveryStationValidationErrors(DeliveryStation station) {
        return isBlank(station.getFirstName()) || isBlank(station.getLastName()) || isBlank(station.getStreet())
                || isBlank(station.getHouseNumber()) || isBlank(station.getZip()) || isBlank(station.getCity());
    }

    private boolean isBlank(String value) {
        return value == null || value.trim().isEmpty();
    }

    private boolean hasAppointmentValidationErrors() {
        LocalDate today = LocalDate.now();
        // Check pickup date
        if (pickupDate.getValue() == null || pickupDate.getValue().isBefore(today)) {
            return true;
        }
        return false;
    }

    private boolean hasCargoValidationErrors() {
        // Check if ALL cargo items have all required fields filled
        // When multiple cargo rows exist, ALL must be complete
        if (cargoItemsState.isEmpty()) {
            return true; // No cargo items at all - show warning
        }

        // Check that ALL cargo items are complete
        // A cargo item is considered complete if it has: Description, Quantity, Weight,
        // Length, Width, Height
        boolean allCargoItemsValid = cargoItemsState.stream()
                .allMatch(cargoItem -> cargoItem != null && cargoItem.getDescription() != null
                        && !cargoItem.getDescription().trim().isEmpty() && cargoItem.getQuantity() != null
                        && cargoItem.getQuantity() > 0 && cargoItem.getWeightKg() != null && cargoItem.getWeightKg() > 0
                        && cargoItem.getLengthMm() != null && cargoItem.getLengthMm() > 0
                        && cargoItem.getWidthMm() != null && cargoItem.getWidthMm() > 0
                        && cargoItem.getHeightMm() != null && cargoItem.getHeightMm() > 0);

        return !allCargoItemsValid; // Return true if ANY cargo item is incomplete (show warning)
    }

    private boolean hasTasksValidationErrors() {
        // Validate tasks across all delivery stations
        for (List<BaseTask> stationTasks : deliveryStationTasksState.values()) {
            for (BaseTask task : stationTasks) {
                if (task instanceof ConfirmationTask confirmationTask) {
                    String description = task.getDescription();
                    if (description == null || description.trim().isEmpty()) {
                        return true;
                    }
                    String buttonText = confirmationTask.getButtonText();
                    if (buttonText == null || buttonText.trim().isEmpty()) {
                        return true;
                    }
                }
                if (task instanceof TodoListTask todoListTask) {
                    List<String> todoItems = todoListTask.getTodoItems();
                    if (todoItems == null || todoItems.isEmpty()) {
                        return true;
                    }
                    boolean hasValidTodoItem = todoItems.stream()
                            .anyMatch(item -> item != null && !item.trim().isEmpty());
                    if (!hasValidTodoItem) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    private boolean hasPriceValidationErrors() {
        // Price tab is valid when at least one service is selected
        return selectedServices == null || selectedServices.isEmpty();
    }

    private boolean isFieldEmpty(TextField field) {
        String value = field.getValue();
        return value == null || value.trim().isEmpty();
    }

    private void submit() {
        try {
            Job job = new Job();

            // Zusätzliche Felder, die nicht über den Binder gebunden sind, manuell setzen
            job.setPickupDate(pickupDate.getValue());
            job.setPickupTime(pickupTime.getValue());
            if (remarkArea != null)
                job.setRemark(remarkArea.getValue());

            // Collect delivery stations from state
            List<DeliveryStation> stations = new ArrayList<>();
            for (int i = 0; i < deliveryStationsState.size(); i++) {
                DeliveryStation station = deliveryStationsState.get(i);
                station.setStationOrder(i);
                station.setTasks(new ArrayList<>(deliveryStationTasksState.getOrDefault(i, List.of())));
                stations.add(station);
            }
            job.setDeliveryStations(stations);

            // Populate flat delivery fields from first station for backward compatibility
            job.syncFlatDeliveryFieldsFromStations();

            // Store selected service IDs in job for invoice creation
            job.setServiceIds(selectedServices.stream().map(entry -> entry.getService().getId()).toList());
            job.setSelectedServices(selectedServices.stream().map(this::toJobServiceSelection).toList());

            // Validate all required fields using the binder
            if (binder.writeBeanIfValid(job)) {
                // Preis nach dem Binder-Call berechnen (damit er nicht überschrieben wird)
                BigDecimal netTotal = selectedServices
                        .stream().map(entry -> calculateServicePrice(entry.getService(),
                                getEffectiveRouteDistance(entry), getEffectiveRouteDuration(entry)))
                        .reduce(BigDecimal.ZERO, BigDecimal::add);
                job.setPrice(netTotal);

                // Store route distance and duration in job for invoice creation
                if (routeCalculationResult != null && routeCalculationResult.isValid()) {
                    // Berechnete Route verwenden
                    job.setRouteDistanceKm(routeCalculationResult.getDistanceKm());
                    job.setRouteDurationSeconds(routeCalculationResult.getDurationSeconds());
                } else if (manualDistanceInput != null && manualDistanceInput.getValue() != null) {
                    // Manuelle Eingabe verwenden
                    job.setRouteDistanceKm(manualDistanceInput.getValue());
                    if (manualDurationInput != null && manualDurationInput.getValue() != null) {
                        job.setRouteDurationSeconds(manualDurationInput.getValue() * 60); // Minuten in Sekunden
                                                                                          // umrechnen
                    }
                }
                // Additional validation: If digital processing is enabled, app user must be
                // selected
                if (digitalProcessing.getValue() && appUser.getValue() == null) {
                    Notification errorNotification = Notification
                            .show(getTranslation("addjob.validation.appuser.required"));
                    errorNotification.setDuration(5000);
                    return;
                }

                // Ensure at least one cargo item is provided (tasks may be empty)
                // Definition: Ein Cargo-Item gilt nur als gefüllt, wenn eine Beschreibung
                // vorhanden ist
                List<CargoItem> cargoFilled = cargoItemsState.stream().filter(Objects::nonNull)
                        .filter(ci -> ci.getDescription() != null && !ci.getDescription().isBlank()).toList();

                if (cargoFilled.isEmpty()) {
                    Notification errorNotification = Notification
                            .show(getTranslation("addjob.validation.cargo.required"));
                    errorNotification.setDuration(5000);
                    return;
                }

                // Kunden anlegen/aktualisieren bzw. intern sichern
                Customer pickupCustomer = new Customer();
                pickupCustomer.setCompanyName(pickupCompany.getValue());
                pickupCustomer.setTitle(pickupSalutation.getValue());
                pickupCustomer.setFirstname(pickupFirstName.getValue());
                pickupCustomer.setLastName(pickupLastName.getValue());
                pickupCustomer.setTelephone(pickupPhone.getValue());
                pickupCustomer.setMail(pickupMail);
                pickupCustomer.setStreet(pickupStreet.getValue());
                pickupCustomer.setHouseNumber(pickupHouseNumber.getValue());
                pickupCustomer.setAddressAddition(pickupAddressAddition.getValue());
                pickupCustomer.setZip(pickupZip.getValue());
                pickupCustomer.setCity(pickupCity.getValue());
                if (savePickupAddress.getValue()) {
                    if (pickupCustomerId != null) {
                        pickupCustomer.setId(pickupCustomerId);
                        addCustomerService.updateCustomer(pickupCustomer);
                    } else {
                        addCustomerService.addCustomer(pickupCustomer);
                    }
                } else if (pickupAddressDiffers) {
                    addCustomerService.addInternalCustomer(pickupCustomer);
                }
                // Delivery-Stationen: anlegen, aktualisieren oder als intern sichern
                for (int i = 0; i < deliveryStationsState.size(); i++) {
                    DeliveryStation ds = deliveryStationsState.get(i);
                    Customer deliveryCustomer = new Customer();
                    deliveryCustomer.setCompanyName(ds.getCompany());
                    deliveryCustomer.setTitle(ds.getSalutation());
                    deliveryCustomer.setFirstname(ds.getFirstName());
                    deliveryCustomer.setLastName(ds.getLastName());
                    deliveryCustomer.setTelephone(ds.getPhone());
                    deliveryCustomer.setMail(i < deliveryStationsMailState.size() ? deliveryStationsMailState.get(i) : null);
                    deliveryCustomer.setStreet(ds.getStreet());
                    deliveryCustomer.setHouseNumber(ds.getHouseNumber());
                    deliveryCustomer.setAddressAddition(ds.getAddressAddition());
                    deliveryCustomer.setZip(ds.getZip());
                    deliveryCustomer.setCity(ds.getCity());
                    boolean saveRequested = i < deliveryStationsSaveAddress.size()
                            && deliveryStationsSaveAddress.get(i);
                    org.bson.types.ObjectId existingId = i < deliveryStationsCustomerId.size()
                            ? deliveryStationsCustomerId.get(i)
                            : null;
                    boolean addressDiffers = i < deliveryStationsAddressDiffers.size()
                            && deliveryStationsAddressDiffers.get(i);
                    if (saveRequested) {
                        if (existingId != null) {
                            deliveryCustomer.setId(existingId);
                            addCustomerService.updateCustomer(deliveryCustomer);
                        } else {
                            addCustomerService.addCustomer(deliveryCustomer);
                        }
                    } else if (addressDiffers) {
                        addCustomerService.addInternalCustomer(deliveryCustomer);
                    }
                }

                // Collect tasks from all delivery stations and set stationOrder
                List<BaseTask> tasksToSave = new ArrayList<>();
                if (job.isDigitalProcessing()) {
                    for (Map.Entry<Integer, List<BaseTask>> entry : deliveryStationTasksState.entrySet()) {
                        int stationIdx = entry.getKey();
                        for (BaseTask task : entry.getValue()) {
                            task.setStationOrder(stationIdx);
                            tasksToSave.add(task);
                        }
                    }
                }
                Job savedJob = addJobService.addJobWithCargo(job, cargoFilled, tasksToSave);

                // Erfolgsmeldung und Navigation zur Zusammenfassung
                Notification successNotification = Notification
                        .show(getTranslation("addjob.notification.success", savedJob.getJobNumber()));
                successNotification.setDuration(2000);
                getUI().ifPresent(ui -> ui.navigate(JobSummaryView.class, savedJob.getId().toHexString()));
            } else {
                // Validation failed, show error message
                Notification errorNotification = Notification.show(getTranslation("addjob.validation.required.fields"));
                errorNotification.setDuration(5000);
            }

        } catch (Exception e) {
            // Other errors
            // Reset cargo error
            Notification errorNotification = Notification
                    .show(getTranslation("addjob.notification.error", e.getMessage()));
            errorNotification.setDuration(5000);
        }
    }

    // showSummary entfernt; Zusammenfassung als eigene Route

    // Zusammenfassungs-Helfer entfernt (Route übernimmt Darstellung)

    /**
     * Lädt verpflichtende Leistungen aus dem Leistungskatalog
     */
    private void loadMandatoryServices() {
        try {
            User currentUser = securityService.getCurrentDatabaseUser();
            if (currentUser != null) {
                List<Service> userServices = serviceRepository.findByUserId(currentUser.getId().toString());
                List<Service> mandatoryServices = userServices.stream().filter(Service::isMandatory).toList();

                if (!mandatoryServices.isEmpty()) {
                    mandatoryServices.stream().map(service -> new SelectedServiceEntry(service, 0))
                            .forEach(selectedServices::add);
                    if (servicesGrid != null) {
                        servicesGrid.getDataProvider().refreshAll();
                    }
                    updatePriceSummary();
                    triggerValidation();
                    updateTabLabels();
                }
            }
        } catch (Exception e) {
            log.warn("Fehler beim Laden der verpflichtenden Leistungen: {}", e.getMessage());
        }
    }

    /**
     * Lädt einen bestehenden Entwurf, falls vorhanden
     */
    private void loadDraftIfExists() {
        try {
            String currentUser = getCurrentUsername();
            if (currentUser != null) {
                Optional<Job> draftOpt = addJobService.findCurrentDraft(currentUser);
                if (draftOpt.isPresent()) {
                    Job draft = draftOpt.get();
                    loadJobIntoForm(draft);

                    // Benutzer informieren
                    Notification notification = Notification.show(getTranslation("addjob.notification.draft.restored"));
                    notification.setDuration(4000);
                }
            }
        } catch (Exception e) {
            // Fehler beim Laden des Entwurfs sollten nicht die Anwendung blockieren
            log.warn("Fehler beim Laden des Entwurfs: {}", e.getMessage());
        }
    }

    /**
     * Lädt Job-Daten in das Formular
     */
    private void loadJobIntoForm(Job job) {
        if (job.getCustomerSelection() != null) {
            setCustomerSelectionValue(job.getCustomerSelection());
        }
    }

    /**
     * Loads task templates for the current user.
     */
    private List<TaskTemplate> loadTaskTemplates() {
        try {
            return taskTemplateService.findByUserId(securityService.getCurrentDatabaseUser().getId());
        } catch (Exception e) {
            log.error("Error loading templates", e);
            return List.of();
        }
    }

    /**
     * Hilfsmethode zum Abrufen des aktuellen Benutzernamens
     */
    private String getCurrentUsername() {
        try {
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            if (authentication != null && authentication.isAuthenticated()
                    && !"anonymousUser".equals(authentication.getPrincipal())) {
                return authentication.getName();
            }
            return null;
        } catch (Exception e) {
            log.warn("Fehler beim Auslesen des aktuellen Benutzers: {}", e.getMessage());
            return null;
        }
    }

    /**
     * Konfiguriert Focus-Listener für alle Eingabefelder um Drag-and-Drop zu
     * steuern
     */
    private void setupInputFieldFocusListeners() {
        // Pickup and delivery fields are now in dialogs - no focus listeners needed

        // Digital processing
        appUser.addFocusListener(e -> disableDragSources());
        appUser.addBlurListener(e -> enableDragSources());
    }

    /**
     * Deaktiviert alle Drag-Sources durch CSS
     */
    private void disableDragSources() {
        if (pickupTile != null) {
            pickupTile.getStyle().set("pointer-events", "none");
        }
    }

    /**
     * Aktiviert alle Drag-Sources durch CSS
     */
    private void enableDragSources() {
        if (pickupTile != null) {
            pickupTile.getStyle().remove("pointer-events");
        }
    }

    // ============================================
    // Adressvalidierung
    // ============================================

    /**
     * Gibt das Validierungsergebnis für die Abholadresse zurück. Kann null sein,
     * wenn noch keine Validierung durchgeführt wurde.
     */
    public AddressValidationResult getPickupAddressValidationResult() {
        return addressValidationResults.get("pickup");
    }

    /**
     * Gibt das Validierungsergebnis für die erste Lieferadresse zurück. Kann null
     * sein, wenn noch keine Validierung durchgeführt wurde.
     */
    public AddressValidationResult getDeliveryAddressValidationResult() {
        return addressValidationResults.get("delivery_0");
    }

    /**
     * Gibt alle Validierungsergebnisse zurück.
     */
    public Map<String, AddressValidationResult> getAddressValidationResults() {
        return new HashMap<>(addressValidationResults);
    }

    /**
     * Gibt das Ergebnis der Routenberechnung zurück. Enthält die Entfernung in
     * Kilometern und die Fahrtzeit, wenn beide Adressen validiert wurden.
     *
     * @return RouteCalculationResult oder null, wenn keine Berechnung durchgeführt
     *         wurde
     */
    public RouteCalculationResult getRouteCalculationResult() {
        return routeCalculationResult;
    }

    /**
     * Gibt die berechnete oder manuell eingegebene Entfernung zurück (für
     * Preisberechnungen).
     *
     * @return Entfernung in km oder 0.0, wenn keine Berechnung oder Eingabe
     *         vorhanden ist
     */
    public double getRouteDistanceKm() {
        if (routeCalculationResult != null && routeCalculationResult.isValid()) {
            return routeCalculationResult.getDistanceKm();
        }
        // Manuelle Eingabe verwenden, wenn vorhanden
        if (manualDistanceInput != null && manualDistanceInput.getValue() != null) {
            return manualDistanceInput.getValue();
        }
        return 0.0;
    }

    /**
     * Gibt die effektive Routendistanz zurück (berechnet oder manuell eingegeben).
     * 
     * @return Distanz als Double oder null, wenn keine Distanz verfügbar
     */
    private Double getEffectiveRouteDistance() {
        if (routeCalculationResult != null && routeCalculationResult.isValid()) {
            return routeCalculationResult.getDistanceKm();
        }
        if (manualDistanceInput != null && manualDistanceInput.getValue() != null) {
            return manualDistanceInput.getValue();
        }
        return null;
    }

    /**
     * Gibt die effektive Fahrtzeit zurück (berechnet oder manuell eingegeben).
     * 
     * @return Dauer in Sekunden oder null, wenn keine Dauer verfügbar
     */
    private Integer getEffectiveRouteDuration() {
        if (routeCalculationResult != null && routeCalculationResult.isValid()) {
            return routeCalculationResult.getDurationSeconds();
        }
        if (manualDurationInput != null && manualDurationInput.getValue() != null) {
            return manualDurationInput.getValue() * 60; // Minuten in Sekunden
        }
        return null;
    }

    /**
     * Entfernt alle leeren (nicht editierten) Lieferstationen, sofern mindestens
     * eine valide Lieferstation übrig bleibt. Wird aufgerufen bevor die Stationen
     * übernommen werden.
     */
    private void removeEmptyDeliveryStations() {
        // Indizes der leeren Stationen sammeln (absteigend, damit beim Entfernen die Indizes stabil bleiben)
        List<Integer> emptyIndices = new ArrayList<>();
        for (int i = 0; i < deliveryStationsState.size(); i++) {
            if (hasDeliveryStationValidationErrors(deliveryStationsState.get(i))) {
                emptyIndices.add(i);
            }
        }

        // Mindestens eine valide Station muss übrig bleiben
        if (emptyIndices.size() >= deliveryStationsState.size()) {
            return;
        }

        // Von hinten nach vorne entfernen, damit Indizes stabil bleiben
        for (int k = emptyIndices.size() - 1; k >= 0; k--) {
            int idx = emptyIndices.get(k);

            deliveryStationTilesList.remove(idx);
            deliveryStationsState.remove(idx);
            deliveryStationsSaveAddress.remove(idx);
            deliveryStationsCustomerId.remove(idx);
            deliveryStationsAddressDiffers.remove(idx);
            deliveryStationsMailState.remove(idx);
            deliveryStationsValidatedByGoogle.remove(idx);
            deliveryStationTasksState.remove(idx);
            Div removedSlot = deliveryStationSlotList.remove(idx);
            deliveryStationDistanceChips.remove(idx);
            pickupToDeliveryRouteResults.remove(idx);
            stationsGridContainer.remove(removedSlot);
        }

        // Tasks und Routen-Maps re-indizieren
        Map<Integer, List<BaseTask>> reindexedTasks = new HashMap<>();
        for (Map.Entry<Integer, List<BaseTask>> entry : deliveryStationTasksState.entrySet()) {
            int oldIdx = entry.getKey();
            int newIdx = oldIdx - (int) emptyIndices.stream().filter(ei -> ei < oldIdx).count();
            reindexedTasks.put(newIdx, entry.getValue());
        }
        deliveryStationTasksState.clear();
        deliveryStationTasksState.putAll(reindexedTasks);

        Map<Integer, RouteCalculationResult> reindexedRoutes = new HashMap<>();
        for (Map.Entry<Integer, RouteCalculationResult> entry : pickupToDeliveryRouteResults.entrySet()) {
            int oldIdx = entry.getKey();
            int newIdx = oldIdx - (int) emptyIndices.stream().filter(ei -> ei < oldIdx).count();
            reindexedRoutes.put(newIdx, entry.getValue());
        }
        pickupToDeliveryRouteResults.clear();
        pickupToDeliveryRouteResults.putAll(reindexedRoutes);

        // Service-Zuordnungen anpassen
        for (SelectedServiceEntry selectedService : selectedServices) {
            Integer stationOrder = selectedService.getDeliveryStationOrder();
            if (stationOrder == null) {
                continue;
            }
            if (emptyIndices.contains(stationOrder)) {
                selectedService.setDeliveryStationOrder(deliveryStationsState.isEmpty() ? null : 0);
            } else {
                int newOrder = stationOrder - (int) emptyIndices.stream().filter(ei -> ei < stationOrder).count();
                selectedService.setDeliveryStationOrder(newOrder);
            }
        }

        // Tiles neu nummerieren und Click-Listener aktualisieren
        for (int i = 0; i < deliveryStationTilesList.size(); i++) {
            StationTile t = deliveryStationTilesList.get(i);
            int newNumber = i + 1;
            t.updateStationNumber(newNumber);
            t.updateTitle(getTranslation("addjob.station.delivery", newNumber));
            final int newIdx = i;
            t.setClickListener(tt -> openDeliveryDialog(tt, newIdx));
            if (i == 0) {
                t.setDeleteListener(null);
            }
        }

        // "+" Button wieder anzeigen falls unter Maximum
        if (deliveryStationTilesList.size() < MAX_DELIVERY_STATIONS
                && addStationButtonSlot.getParent().isEmpty()) {
            stationsGridContainer.add(addStationButtonSlot);
        }

        if (servicesGrid != null) {
            servicesGrid.getDataProvider().refreshAll();
        }
        updatePriceSummary();
        triggerValidation();
        updateTabLabels();
    }

    private void handleApplyStations() {
        removeEmptyDeliveryStations();
        revealPriceAndDetailsSection();

        if (!areAllStationsValidatedByGoogle()) {
            return;
        }

        Dialog loadingDialog = createRouteLoadingDialog();
        UI ui = UI.getCurrent();
        loadingDialog.open();

        CompletableFuture.supplyAsync(this::calculateRouteBundle).whenComplete((routeBundle, throwable) -> {
            if (ui == null) {
                return;
            }
            ui.access(() -> {
                loadingDialog.close();

                if (throwable != null) {
                    log.error("Fehler bei der Berechnung der Gesamtstrecke", throwable);
                    Notification.show("Die Strecke konnte nicht berechnet werden.", 4000, Notification.Position.MIDDLE);
                    return;
                }

                RouteCalculationResult routeResult = routeBundle != null ? routeBundle.totalRoute() : null;
                if (routeResult != null && routeResult.isValid()) {
                    applyCalculatedRoutes(routeBundle);
                    showRouteSummaryDialog(routeResult);
                } else {
                    String message = routeResult != null && routeResult.getRouteMessage() != null
                            ? routeResult.getRouteMessage()
                            : "Die Strecke konnte nicht berechnet werden.";
                    Notification.show(message, 4000, Notification.Position.MIDDLE);
                }
            });
        });
    }

    private void revealPriceAndDetailsSection() {
        applyStationsButton.setVisible(false);
        priceAndDetailsSection.setVisible(true);
        submitButtonLayout.setVisible(true);
    }

    private boolean areAllStationsValidatedByGoogle() {
        if (!pickupAddressValidatedByGoogle || deliveryStationsState.isEmpty()) {
            return false;
        }
        if (deliveryStationsValidatedByGoogle.size() != deliveryStationsState.size()) {
            return false;
        }
        return deliveryStationsValidatedByGoogle.stream().allMatch(Boolean.TRUE::equals);
    }

    private RouteCalculationBundle calculateRouteBundle() {
        if (!pickupAddressValidatedByGoogle) {
            return createInvalidRouteBundle("Die Abholstation ist nicht validiert.");
        }

        AddressValidationResult pickupValidation = getOrValidatePickupAddressResult();
        if (pickupValidation == null || !pickupValidation.isValid()) {
            return createInvalidRouteBundle("Die Abholstation konnte nicht validiert werden.");
        }

        List<AddressValidationResult> deliveryValidations = new ArrayList<>();

        for (int i = 0; i < deliveryStationsState.size(); i++) {
            DeliveryStation station = deliveryStationsState.get(i);
            if (hasDeliveryStationValidationErrors(station)) {
                return createInvalidRouteBundle("Nicht alle Lieferstationen sind vollständig ausgefüllt.");
            }
            if (i >= deliveryStationsValidatedByGoogle.size()
                    || !Boolean.TRUE.equals(deliveryStationsValidatedByGoogle.get(i))) {
                return createInvalidRouteBundle("Nicht alle Lieferstationen sind validiert.");
            }

            AddressValidationResult deliveryValidation = getOrValidateDeliveryAddressResult(i);
            if (deliveryValidation == null || !deliveryValidation.isValid()) {
                return createInvalidRouteBundle(
                        String.format("Die Strecke konnte für Lieferstation %d nicht berechnet werden.", i + 1));
            }
            deliveryValidations.add(deliveryValidation);
        }

        Map<Integer, RouteCalculationResult> deliveryRoutes = new HashMap<>();
        AddressValidationResult previousStation = pickupValidation;
        for (int i = 0; i < deliveryValidations.size(); i++) {
            AddressValidationResult currentStation = deliveryValidations.get(i);
            RouteCalculationResult legRoute = addressValidationService.calculateRoute(previousStation, currentStation);
            if (legRoute == null || !legRoute.isValid()) {
                return createInvalidRouteBundle(
                        String.format("Die Strecke konnte für Lieferstation %d nicht berechnet werden.", i + 1));
            }
            deliveryRoutes.put(i, legRoute);
            previousStation = currentStation;
        }

        RouteCalculationResult totalRoute = aggregateLegRoutes(deliveryRoutes, deliveryValidations.size());
        return new RouteCalculationBundle(totalRoute, deliveryRoutes);
    }

    private RouteCalculationResult aggregateLegRoutes(Map<Integer, RouteCalculationResult> deliveryRoutes,
            int legCount) {
        if (deliveryRoutes == null || deliveryRoutes.size() != legCount || legCount == 0) {
            return createInvalidRouteResult("Die Gesamtstrecke konnte nicht berechnet werden.");
        }

        double totalDistanceKm = 0.0;
        int totalDurationSeconds = 0;

        for (int i = 0; i < legCount; i++) {
            RouteCalculationResult legRoute = deliveryRoutes.get(i);
            if (legRoute == null || !legRoute.isValid()) {
                return createInvalidRouteResult("Die Gesamtstrecke konnte nicht berechnet werden.");
            }
            totalDistanceKm += legRoute.getDistanceKm();
            totalDurationSeconds += legRoute.getDurationSeconds();
        }

        RouteCalculationResult totalRoute = new RouteCalculationResult();
        totalRoute.setValid(true);
        totalRoute.setDistanceKm(totalDistanceKm);
        totalRoute.setDurationSeconds(totalDurationSeconds);
        totalRoute.setFormattedDistance(String.format(Locale.GERMANY, "%.1f km", totalDistanceKm));
        totalRoute.setFormattedDuration(totalRoute.getFormattedDurationLong());
        totalRoute.setRouteMessage(String.format("Route: %s, Dauer: %s", totalRoute.getFormattedDistance(),
                totalRoute.getFormattedDurationLong()));
        return totalRoute;
    }

    private RouteCalculationResult createInvalidRouteResult(String message) {
        RouteCalculationResult routeResult = new RouteCalculationResult();
        routeResult.setRouteMessage(message);
        return routeResult;
    }

    private AddressValidationResult getOrValidatePickupAddressResult() {
        AddressValidationResult existingResult = addressValidationResults.get("pickup");
        if (existingResult != null && existingResult.matches(pickupStreet.getValue(), pickupHouseNumber.getValue(),
                pickupZip.getValue(), pickupCity.getValue())) {
            return existingResult;
        }

        AddressValidationResult validationResult = addressValidationService.validateAddress("pickup",
                pickupStreet.getValue(), pickupHouseNumber.getValue(), pickupZip.getValue(), pickupCity.getValue());
        storePickupAddressValidationResult(validationResult);
        return validationResult;
    }

    private AddressValidationResult getOrValidateDeliveryAddressResult(int index) {
        String resultKey = "delivery_" + index;
        DeliveryStation station = deliveryStationsState.get(index);

        AddressValidationResult existingResult = addressValidationResults.get(resultKey);
        if (existingResult != null && existingResult.matches(station.getStreet(), station.getHouseNumber(),
                station.getZip(), station.getCity())) {
            return existingResult;
        }

        AddressValidationResult validationResult = addressValidationService.validateAddress(resultKey,
                station.getStreet(), station.getHouseNumber(), station.getZip(), station.getCity());
        storeDeliveryAddressValidationResult(index, validationResult);
        return validationResult;
    }

    private void storePickupAddressValidationResult(AddressValidationResult validationResult) {
        if (validationResult == null) {
            addressValidationResults.remove("pickup");
            return;
        }
        addressValidationResults.put("pickup", validationResult);
    }

    private void storeDeliveryAddressValidationResult(int index, AddressValidationResult validationResult) {
        String resultKey = "delivery_" + index;
        if (validationResult == null) {
            addressValidationResults.remove(resultKey);
            return;
        }
        addressValidationResults.put(resultKey, validationResult);
    }

    private RouteCalculationBundle createInvalidRouteBundle(String message) {
        return new RouteCalculationBundle(createInvalidRouteResult(message), Map.of());
    }

    private Dialog createRouteLoadingDialog() {
        Dialog dialog = DialogStylingHelper.createStyledDialog(getTranslation("addjob.route.title"), "460px");
        dialog.setCloseOnOutsideClick(false);
        dialog.setCloseOnEsc(false);

        VerticalLayout content = DialogStylingHelper.createContentLayout("340px", FlexComponent.Alignment.CENTER);
        content.setAlignItems(FlexComponent.Alignment.CENTER);
        content.getStyle().set("text-align", "center");

        Span loadingText = new Span("Strecke zwischen allen Stationen wird berechnet...");
        ProgressBar progressBar = new ProgressBar();
        progressBar.setIndeterminate(true);
        progressBar.setWidthFull();

        content.add(loadingText, progressBar);
        dialog.add(DialogStylingHelper.wrapContent(content));
        return dialog;
    }

    private void applyCalculatedRoutes(RouteCalculationBundle routeBundle) {
        RouteCalculationResult routeResult = routeBundle.totalRoute();
        routeCalculationResult = routeResult;
        pickupToDeliveryRouteResults.clear();
        if (routeBundle.deliveryRoutes() != null) {
            pickupToDeliveryRouteResults.putAll(routeBundle.deliveryRoutes());
        }
        renderDeliveryStationDistanceChips();

        routeDistanceLabel.setText(routeResult.getFormattedDistance());
        routeDurationLabel.setText(routeResult.getFormattedDurationLong());
        routeInfoBox.setVisible(true);
        manualRouteInputBox.setVisible(false);

        updatePriceSummary();
        if (servicesGrid != null) {
            servicesGrid.getDataProvider().refreshAll();
        }
    }

    private void showRouteSummaryDialog(RouteCalculationResult routeResult) {
        Dialog dialog = DialogStylingHelper.createStyledDialog(getTranslation("addjob.route.title"), "480px");

        VerticalLayout content = DialogStylingHelper.createContentLayout("360px");
        content.add(createRouteSummaryRow(getTranslation("addjob.route.distance"), routeResult.getFormattedDistance()));
        content.add(
                createRouteSummaryRow(getTranslation("addjob.route.duration"), routeResult.getFormattedDurationLong()));

        Button closeButton = new Button(getTranslation("dialog.confirm"), event -> dialog.close());
        closeButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);

        dialog.add(DialogStylingHelper.wrapContent(content));
        dialog.getFooter().add(closeButton);
        dialog.open();
    }

    private HorizontalLayout createRouteSummaryRow(String label, String value) {
        HorizontalLayout row = new HorizontalLayout();
        row.setWidthFull();
        row.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);
        row.setAlignItems(FlexComponent.Alignment.CENTER);

        Span labelSpan = new Span(label + ":");
        Span valueSpan = new Span(value);
        valueSpan.getStyle().set("font-weight", "bold");

        row.add(labelSpan, valueSpan);
        return row;
    }

    private void renderDeliveryStationDistanceChips() {
        if (deliveryStationDistanceChips.isEmpty()) {
            return;
        }

        for (int i = 0; i < deliveryStationDistanceChips.size(); i++) {
            Span chip = deliveryStationDistanceChips.get(i);
            RouteCalculationResult stationRoute = pickupToDeliveryRouteResults.get(i);
            if (stationRoute == null || !stationRoute.isValid()) {
                chip.setText("");
                chip.setVisible(false);
                continue;
            }

            chip.setText(stationRoute.getFormattedDistance());
            chip.setVisible(true);
        }
    }

    /**
     * Registriert ValueChangeListener für alle Adressfelder, um bei Änderungen die
     * Streckeninformationen zurückzusetzen.
     */
    private void setupAddressChangeListeners() {
        // Abholadress-Felder
        pickupCompany.addValueChangeListener(e -> {
            if (e.isFromClient()) {
                resetRouteInformation();
            }
        });
        pickupStreet.addValueChangeListener(e -> {
            if (e.isFromClient()) {
                resetRouteInformation();
            }
        });
        pickupHouseNumber.addValueChangeListener(e -> {
            if (e.isFromClient()) {
                resetRouteInformation();
            }
        });
        pickupZip.addValueChangeListener(e -> {
            if (e.isFromClient()) {
                resetRouteInformation();
            }
        });
        pickupCity.addValueChangeListener(e -> {
            if (e.isFromClient()) {
                resetRouteInformation();
            }
        });

        // Delivery station field change listeners are handled via tile callbacks
    }

    /**
     * Setzt alle Streckeninformationen zurück, wenn sich Adressdaten ändern. Dies
     * bewirkt, dass der Validierungsdialog beim Tab-Wechsel erneut angezeigt wird.
     */
    private void resetStationsAppliedState() {
        if (applyStationsButton != null) {
            applyStationsButton.setVisible(true);
        }
        if (priceAndDetailsSection != null) {
            priceAndDetailsSection.setVisible(false);
        }
        if (submitButtonLayout != null) {
            submitButtonLayout.setVisible(false);
        }
        updateApplyStationsButtonState();
    }

    /**
     * Aktiviert den "Stationen übernehmen"-Button nur, wenn die Abholstation und
     * mindestens eine Lieferstation korrekt mit Daten gefüllt sind.
     */
    private void updateApplyStationsButtonState() {
        if (applyStationsButton == null) {
            return;
        }
        // Pickup-Adresse prüfen
        boolean pickupValid = !isFieldEmpty(pickupFirstName) && !isFieldEmpty(pickupLastName)
                && !isFieldEmpty(pickupStreet) && !isFieldEmpty(pickupHouseNumber) && !isFieldEmpty(pickupZip)
                && !isFieldEmpty(pickupCity) && pickupDate.getValue() != null
                && !pickupDate.getValue().isBefore(LocalDate.now());

        // Mindestens eine Lieferstation muss korrekt gefüllt sein
        boolean atLeastOneDeliveryValid = deliveryStationsState.stream()
                .anyMatch(station -> !hasDeliveryStationValidationErrors(station));

        applyStationsButton.setEnabled(pickupValid && atLeastOneDeliveryValid);
    }

    private void resetRouteInformation() {
        // Routenberechnung zurücksetzen
        routeCalculationResult = null;
        pickupToDeliveryRouteResults.clear();

        // Validierungsergebnisse zurücksetzen
        addressValidationResults.clear();

        // Route-Info-Box verstecken und manuelle Eingabe anzeigen
        if (routeInfoBox != null) {
            routeInfoBox.setVisible(false);
        }
        if (routeDistanceLabel != null) {
            routeDistanceLabel.setText("-");
        }
        if (routeDurationLabel != null) {
            routeDurationLabel.setText("-");
        }
        if (manualRouteInputBox != null) {
            manualRouteInputBox.setVisible(true);
        }
        for (Span chip : deliveryStationDistanceChips) {
            chip.setText("");
            chip.setVisible(false);
        }

        log.debug("Streckeninformationen zurückgesetzt aufgrund von Adressänderungen");
    }

}
