package de.votian.web.view;

import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.checkbox.CheckboxGroup;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.datepicker.DatePicker;
import com.vaadin.flow.component.dialog.Dialog;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.html.H3;
import com.vaadin.flow.component.html.Paragraph;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.notification.NotificationVariant;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.tabs.Tab;
import com.vaadin.flow.component.tabs.Tabs;
import com.vaadin.flow.component.textfield.PasswordField;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.router.*;
import de.votian.web.model.SessionData;
import de.votian.web.service.RestClientService;
import org.springframework.core.ParameterizedTypeReference;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

@Route(value = "couriers/:id?", layout = MainLayout.class)
@PageTitle("Votian - Kurierdetail")
public class CourierDetailView extends VerticalLayout implements HasUrlParameter<String> {

    private final RestClientService restClient;
    private final SessionData sessionData;
    private Long courierId;
    private boolean isNew = true;

    private final TextField compField = new TextField("Firma");
    private final TextField comp2Field = new TextField("Firma 2");
    private final TextField hsnoField = new TextField("Hausnummer");
    private final TextField streetField = new TextField("Strasse");
    private final TextField zipcodeField = new TextField("PLZ");
    private final TextField cityField = new TextField("Ort");
    private final TextField ilnField = new TextField("ILN");
    private final TextField taxIdField = new TextField("Steuer-Nr.");

    private final TextField eidField = new TextField("Kurier-Nr. (EID)");
    private final TextField sidField = new TextField("SID");
    private final ComboBox<String> availableField = new ComboBox<>("Verfuegbar");
    private final TextField mobilePdaField = new TextField("Mobile/PDA");
    private final TextField trackingZipcodeField = new TextField("Tracking-PLZ");
    private final TextField trackingGpsTimeField = new TextField("Letzte Ortung");
    private final TextField trackingGpsTypeField = new TextField("Ortungstyp");
    private final TextField trackingGpsCoordinatesField = new TextField("Koordinaten");
    private final TextField trackingAvailableTimeField = new TextField("Verfuegbar seit");
    private final CheckboxGroup<GroupOption> groupField = new CheckboxGroup<>();
    private final CheckboxGroup<FilterOption> filterField = new CheckboxGroup<>();

    private final TextField accountField = new TextField("Benutzerkonto");
    private final TextField nameField = new TextField("Name");
    private final TextField firstnameField = new TextField("Vorname");
    private final TextField emailField = new TextField("E-Mail");
    private final TextField phoneField = new TextField("Telefon");
    private final TextField phone2Field = new TextField("Telefon 2");
    private final TextField faxField = new TextField("Fax");
    private final DatePicker birthdateField = new DatePicker("Geburtsdatum");
    private final TextField countryField = new TextField("Land");
    private final PasswordField passwordField = new PasswordField("Passwort");

    private final VerticalLayout vehicleContent = new VerticalLayout();
    private final Grid<VehicleRow> vehicleGrid = new Grid<>(VehicleRow.class, false);
    private final Button addVehicleButton = new Button("Fahrzeug anlegen", VaadinIcon.PLUS.create());
    private final Button editVehicleButton = new Button("Fahrzeug bearbeiten", VaadinIcon.EDIT.create());
    private final Button deleteVehicleButton = new Button("Fahrzeug loeschen", VaadinIcon.TRASH.create());

    private final List<VehicleTypeOption> vehicleTypes = new ArrayList<>();
    private final List<VehicleRow> vehicles = new ArrayList<>();
    private final List<GroupOption> groupOptions = new ArrayList<>();
    private final List<FilterOption> filterOptions = new ArrayList<>();

    public CourierDetailView(RestClientService restClient, SessionData sessionData) {
        this.restClient = restClient;
        this.sessionData = sessionData;
        setPadding(true);
        setSpacing(true);
        setWidthFull();

        availableField.setItems("0", "1");
        availableField.setItemLabelGenerator(v -> "1".equals(v) ? "Ja" : "Nein");
        mobilePdaField.setWidthFull();
        trackingZipcodeField.setReadOnly(true);
        trackingGpsTimeField.setReadOnly(true);
        trackingGpsTypeField.setReadOnly(true);
        trackingGpsCoordinatesField.setReadOnly(true);
        trackingAvailableTimeField.setReadOnly(true);
        groupField.setLabel("Gruppen");
        groupField.setWidthFull();
        groupField.setItemLabelGenerator(GroupOption::label);
        filterField.setLabel("Filter");
        filterField.setWidthFull();
        filterField.setItemLabelGenerator(FilterOption::label);

        vehicleContent.setPadding(false);
        vehicleContent.setSpacing(true);
        vehicleContent.setWidthFull();

        configureVehicleGrid();
        configureVehicleActions();
    }

    @Override
    public void setParameter(BeforeEvent event, @OptionalParameter String parameter) {
        removeAll();
        resetState();
        loadVehicleTypes();
        loadGroupFilterOptions();
        if (parameter != null && !parameter.isEmpty() && !"new".equals(parameter)) {
            try {
                courierId = Long.parseLong(parameter);
                isNew = false;
                loadCourier();
                loadVehicles();
            } catch (NumberFormatException e) {
                isNew = true;
            }
        }
        buildForm();
    }

    private void buildForm() {
        H3 title = new H3(isNew ? "Neuer Kurier" : "Kurier bearbeiten");

        Tab companyTab = new Tab("Firma");
        Tab contactTab = new Tab("Kontakt");
        Tab courierTab = new Tab("Kurier-Daten");
        Tab vehiclesTab = new Tab("Fahrzeuge");
        Tabs tabs = new Tabs(companyTab, contactTab, courierTab, vehiclesTab);

        FormLayout companyForm = new FormLayout();
        companyForm.setResponsiveSteps(new FormLayout.ResponsiveStep("0", 1),
                new FormLayout.ResponsiveStep("500px", 2));
        companyForm.add(compField, comp2Field, streetField, hsnoField, zipcodeField, cityField, ilnField, taxIdField);

        FormLayout contactForm = new FormLayout();
        contactForm.setResponsiveSteps(new FormLayout.ResponsiveStep("0", 1),
                new FormLayout.ResponsiveStep("500px", 2));
        contactForm.add(nameField, firstnameField, emailField, phoneField, phone2Field, faxField,
                birthdateField, countryField, accountField, passwordField);
        contactForm.setVisible(false);

        FormLayout courierForm = new FormLayout();
        courierForm.setResponsiveSteps(new FormLayout.ResponsiveStep("0", 1),
                new FormLayout.ResponsiveStep("500px", 2));
        courierForm.add(
                eidField,
                sidField,
                availableField,
                mobilePdaField,
                trackingZipcodeField,
                trackingGpsTimeField,
                trackingGpsTypeField,
                trackingGpsCoordinatesField,
                trackingAvailableTimeField,
                groupField,
                filterField
        );
        courierForm.setColspan(trackingGpsCoordinatesField, 2);
        courierForm.setColspan(groupField, 2);
        courierForm.setColspan(filterField, 2);
        courierForm.setVisible(false);

        buildVehicleContent();
        vehicleContent.setVisible(false);

        tabs.addSelectedChangeListener(e -> {
            companyForm.setVisible(e.getSelectedTab() == companyTab);
            contactForm.setVisible(e.getSelectedTab() == contactTab);
            courierForm.setVisible(e.getSelectedTab() == courierTab);
            vehicleContent.setVisible(e.getSelectedTab() == vehiclesTab);
        });

        Button saveButton = new Button("Speichern", VaadinIcon.CHECK.create(), e -> save());
        saveButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
        saveButton.setEnabled(sessionData.canManageCouriers());
        Button cancelButton = new Button("Abbrechen", e ->
                getUI().ifPresent(ui -> ui.navigate(CourierListView.class)));

        HorizontalLayout buttons = new HorizontalLayout(saveButton, cancelButton);
        add(title, tabs, companyForm, contactForm, courierForm, vehicleContent, buttons);
    }

    private void configureVehicleGrid() {
        vehicleGrid.addColumn(VehicleRow::sid).setHeader("SID").setAutoWidth(true);
        vehicleGrid.addColumn(VehicleRow::vehicleTypeName).setHeader("Fahrzeugtyp").setAutoWidth(true);
        vehicleGrid.setWidthFull();
        vehicleGrid.setMinHeight("240px");
        vehicleGrid.asSingleSelect().addValueChangeListener(e -> refreshVehicleActionState());
    }

    private void configureVehicleActions() {
        addVehicleButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
        editVehicleButton.addClickListener(e -> {
            VehicleRow selectedVehicle = vehicleGrid.asSingleSelect().getValue();
            if (selectedVehicle != null) {
                openVehicleDialog(selectedVehicle);
            }
        });
        deleteVehicleButton.addThemeVariants(ButtonVariant.LUMO_ERROR);
        addVehicleButton.addClickListener(e -> openVehicleDialog(null));
        deleteVehicleButton.addClickListener(e -> deleteSelectedVehicle());
        refreshVehicleActionState();
    }

    private void buildVehicleContent() {
        vehicleContent.removeAll();
        vehicleGrid.setItems(vehicles);
        vehicleGrid.asSingleSelect().clear();

        String message = courierId == null
                ? "Fahrzeuge koennen nach dem ersten Speichern des Kuriers gepflegt werden."
                : "Zusatzfahrzeuge des Kuriers inklusive Fahrzeugtyp pflegen.";
        Paragraph info = new Paragraph(message);
        HorizontalLayout actions = new HorizontalLayout(addVehicleButton, editVehicleButton, deleteVehicleButton);
        vehicleContent.add(info, actions, vehicleGrid);
        refreshVehicleActionState();
    }

    private void openVehicleDialog(VehicleRow existingVehicle) {
        if (!sessionData.canManageCouriers()) {
            Notification.show("Keine Berechtigung zur Fahrzeugpflege.", 3000, Notification.Position.MIDDLE)
                    .addThemeVariants(NotificationVariant.LUMO_ERROR);
            return;
        }
        if (courierId == null) {
            Notification.show("Kurier zuerst speichern.", 3000, Notification.Position.MIDDLE)
                    .addThemeVariants(NotificationVariant.LUMO_ERROR);
            return;
        }

        Dialog dialog = new Dialog();
        dialog.setHeaderTitle(existingVehicle == null ? "Fahrzeug anlegen" : "Fahrzeug bearbeiten");

        TextField vehicleSidField = new TextField("SID");
        vehicleSidField.setWidthFull();
        ComboBox<VehicleTypeOption> vehicleTypeField = new ComboBox<>("Fahrzeugtyp");
        vehicleTypeField.setItems(vehicleTypes);
        vehicleTypeField.setItemLabelGenerator(VehicleTypeOption::label);
        vehicleTypeField.setWidthFull();

        if (existingVehicle != null) {
            vehicleSidField.setValue(existingVehicle.sid());
            vehicleTypeField.setValue(findVehicleType(existingVehicle.vehicleTypeId()));
        }

        Button saveButton = new Button("Speichern", e -> {
            if (vehicleTypeField.getValue() == null) {
                Notification.show("Bitte einen Fahrzeugtyp waehlen.", 3000, Notification.Position.MIDDLE)
                        .addThemeVariants(NotificationVariant.LUMO_ERROR);
                return;
            }

            try {
                Map<String, Object> vehicle = new HashMap<>();
                vehicle.put("sid", vehicleSidField.getValue());
                vehicle.put("vehicleTypeId", vehicleTypeField.getValue().id());

                if (existingVehicle == null) {
                    restClient.post("/couriers/" + courierId + "/vehicles", vehicle, LinkedHashMap.class);
                } else {
                    restClient.put("/couriers/" + courierId + "/vehicles/" + existingVehicle.id(), vehicle);
                }

                loadVehicles();
                vehicleGrid.setItems(vehicles);
                vehicleGrid.asSingleSelect().clear();
                refreshVehicleActionState();
                dialog.close();
            } catch (Exception ex) {
                Notification.show("Fehler: " + ex.getMessage(), 5000, Notification.Position.MIDDLE)
                        .addThemeVariants(NotificationVariant.LUMO_ERROR);
            }
        });
        saveButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);

        Button cancelButton = new Button("Abbrechen", e -> dialog.close());
        HorizontalLayout footer = new HorizontalLayout(saveButton, cancelButton);
        VerticalLayout content = new VerticalLayout(vehicleSidField, vehicleTypeField, footer);
        content.setPadding(false);
        content.setSpacing(true);

        dialog.add(content);
        dialog.open();
    }

    private void deleteSelectedVehicle() {
        if (!sessionData.canManageCouriers()) {
            return;
        }
        VehicleRow selectedVehicle = vehicleGrid.asSingleSelect().getValue();
        if (selectedVehicle == null || courierId == null) {
            return;
        }

        try {
            restClient.delete("/couriers/" + courierId + "/vehicles/" + selectedVehicle.id());
            loadVehicles();
            vehicleGrid.setItems(vehicles);
            vehicleGrid.asSingleSelect().clear();
            refreshVehicleActionState();
            Notification.show("Fahrzeug geloescht.", 3000, Notification.Position.MIDDLE)
                    .addThemeVariants(NotificationVariant.LUMO_SUCCESS);
        } catch (Exception e) {
            Notification.show("Fehler: " + e.getMessage(), 5000, Notification.Position.MIDDLE)
                    .addThemeVariants(NotificationVariant.LUMO_ERROR);
        }
    }

    private void loadCourier() {
        try {
            @SuppressWarnings("unchecked")
            Map<String, Object> courier = restClient.get("/couriers/" + courierId, LinkedHashMap.class);
            if (courier != null) {
                eidField.setValue(str(courier.get("eid")));
                sidField.setValue(str(courier.get("sid")));
                String available = str(courier.get("available"));
                if (!available.isBlank()) {
                    availableField.setValue(available);
                }
                mobilePdaField.setValue(str(courier.get("mobilePda")));
                trackingZipcodeField.setValue(str(courier.get("locationZipcode")));
                trackingGpsTimeField.setValue(dateTimeText(courier.get("gpsTime")));
                trackingGpsTypeField.setValue(gpsTypeLabel(toInteger(courier.get("gpsType"))));
                trackingGpsCoordinatesField.setValue(trackingCoordinates(courier));
                trackingAvailableTimeField.setValue(dateTimeText(courier.get("availableTime")));
                compField.setValue(str(courier.get("companyName")));
                comp2Field.setValue(str(courier.get("companyName2")));
                hsnoField.setValue(str(courier.get("hsno")));
                streetField.setValue(str(courier.get("street")));
                zipcodeField.setValue(str(courier.get("zipcode")));
                cityField.setValue(str(courier.get("city")));
                ilnField.setValue(str(courier.get("iln")));
                taxIdField.setValue(str(courier.get("taxIdNo")));
                accountField.setValue(str(courier.get("account")));
                nameField.setValue(str(courier.get("name")));
                firstnameField.setValue(str(courier.get("firstname")));
                emailField.setValue(str(courier.get("email")));
                phoneField.setValue(str(courier.get("phone")));
                phone2Field.setValue(str(courier.get("phone2")));
                faxField.setValue(str(courier.get("fax")));
                countryField.setValue(str(courier.get("country")));
                groupField.setValue(selectGroups(str(courier.get("group"))));
                filterField.setValue(selectFilters(str(courier.get("filter"))));
                if (courier.get("birthdate") != null) {
                    birthdateField.setValue(java.time.LocalDate.parse(courier.get("birthdate").toString()));
                }
            }
        } catch (Exception e) {
            Notification.show("Fehler: " + e.getMessage());
        }
    }

    private void loadVehicleTypes() {
        try {
            List<LinkedHashMap<String, Object>> result = restClient.getList(
                    "/metatypes/vehicletypes",
                    new ParameterizedTypeReference<>() {}
            );
            for (Map<String, Object> row : result) {
                Integer id = toInteger(row.get("id"));
                if (id != null) {
                    vehicleTypes.add(new VehicleTypeOption(id, str(row.get("value"))));
                }
            }
        } catch (Exception e) {
            Notification.show("Fahrzeugtypen konnten nicht geladen werden: " + e.getMessage(),
                    5000, Notification.Position.MIDDLE).addThemeVariants(NotificationVariant.LUMO_ERROR);
        }
    }

    private void loadGroupFilterOptions() {
        if (sessionData.getHeadquartersId() == null) {
            return;
        }
        try {
            groupOptions.clear();
            List<LinkedHashMap<String, Object>> groups = restClient.getList(
                    "/group-filter-admin/groups?hqId=" + sessionData.getHeadquartersId(),
                    new ParameterizedTypeReference<>() {}
            );
            for (Map<String, Object> row : groups) {
                Long id = toLong(row.get("id"));
                if (id != null) {
                    groupOptions.add(new GroupOption(id, str(row.get("name"))));
                }
            }
            groupField.setItems(groupOptions);

            filterOptions.clear();
            List<LinkedHashMap<String, Object>> filters = restClient.getList(
                    "/group-filter-admin/filters?hqId=" + sessionData.getHeadquartersId(),
                    new ParameterizedTypeReference<>() {}
            );
            for (Map<String, Object> row : filters) {
                String shortCode = str(row.get("shortCode"));
                if (!shortCode.isBlank()) {
                    filterOptions.add(new FilterOption(shortCode, str(row.get("text"))));
                }
            }
            filterField.setItems(filterOptions);
        } catch (Exception e) {
            Notification.show("Gruppen- und Filterkatalog konnte nicht geladen werden: " + e.getMessage(),
                    5000, Notification.Position.MIDDLE).addThemeVariants(NotificationVariant.LUMO_ERROR);
        }
    }

    private void loadVehicles() {
        vehicles.clear();
        if (courierId == null) {
            return;
        }

        try {
            List<LinkedHashMap<String, Object>> result = restClient.getList(
                    "/couriers/" + courierId + "/vehicles",
                    new ParameterizedTypeReference<>() {}
            );
            for (Map<String, Object> row : result) {
                Long id = toLong(row.get("id"));
                Integer vehicleTypeId = toInteger(row.get("vehicleTypeId"));
                vehicles.add(new VehicleRow(
                        id,
                        str(row.get("sid")),
                        vehicleTypeId,
                        findVehicleTypeLabel(vehicleTypeId)
                ));
            }
        } catch (Exception e) {
            Notification.show("Kurierfahrzeuge konnten nicht geladen werden: " + e.getMessage(),
                    5000, Notification.Position.MIDDLE).addThemeVariants(NotificationVariant.LUMO_ERROR);
        }
    }

    private void save() {
        if (!sessionData.canManageCouriers()) {
            Notification.show("Keine Berechtigung zum Bearbeiten von Kurieren.", 3000, Notification.Position.MIDDLE)
                    .addThemeVariants(NotificationVariant.LUMO_ERROR);
            return;
        }
        try {
            boolean createdNow = isNew;
            Map<String, Object> request = new HashMap<>();
            Map<String, Object> courier = new HashMap<>();
            courier.put("headquartersId", sessionData.getHeadquartersId());
            courier.put("eid", eidField.getValue());
            courier.put("sid", sidField.getValue());
            courier.put("available", availableField.getValue());
            courier.put("mobilePda", mobilePdaField.getValue());
            courier.put("group", toGroupString(groupField.getValue()));
            courier.put("filter", toFilterString(filterField.getValue()));
            request.put("courier", courier);

            Map<String, Object> company = new HashMap<>();
            company.put("comp", compField.getValue());
            company.put("comp2", comp2Field.getValue());
            company.put("hsno", hsnoField.getValue());
            company.put("iln", ilnField.getValue());
            company.put("taxIdNo", taxIdField.getValue());
            request.put("company", company);

            Map<String, Object> address = new HashMap<>();
            address.put("street", streetField.getValue());
            address.put("zipcode", zipcodeField.getValue());
            address.put("city", cityField.getValue());
            address.put("country", countryField.getValue());
            request.put("address", address);

            Map<String, Object> user = new HashMap<>();
            user.put("account", accountField.getValue());
            user.put("name", nameField.getValue());
            user.put("firstname", firstnameField.getValue());
            user.put("email", emailField.getValue());
            user.put("phone", phoneField.getValue());
            user.put("phone2", phone2Field.getValue());
            user.put("fax", faxField.getValue());
            user.put("birthdate", birthdateField.getValue());
            user.put("country", countryField.getValue());
            request.put("user", user);

            if (isNew) {
                request.put("password", passwordField.getValue());
                @SuppressWarnings("unchecked")
                Map<String, Object> created = restClient.post("/couriers", request, LinkedHashMap.class);
                courierId = created != null ? toLong(created.get("id")) : null;
                isNew = false;
            } else {
                restClient.put("/couriers/" + courierId, request);
            }

            Notification.show("Kurier gespeichert!", 3000, Notification.Position.MIDDLE)
                    .addThemeVariants(NotificationVariant.LUMO_SUCCESS);

            if (createdNow && courierId != null && getUI().isPresent()) {
                getUI().get().navigate("couriers/" + courierId);
            }
        } catch (Exception e) {
            Notification.show("Fehler: " + e.getMessage(), 5000, Notification.Position.MIDDLE)
                    .addThemeVariants(NotificationVariant.LUMO_ERROR);
        }
    }

    private void refreshVehicleActionState() {
        VehicleRow selectedVehicle = vehicleGrid.asSingleSelect().getValue();
        boolean editable = sessionData.canManageCouriers() && courierId != null && !isNew;
        addVehicleButton.setEnabled(editable);
        editVehicleButton.setEnabled(editable && selectedVehicle != null);
        deleteVehicleButton.setEnabled(editable && selectedVehicle != null);
    }

    private void resetState() {
        courierId = null;
        isNew = true;
        vehicleTypes.clear();
        vehicles.clear();
        groupOptions.clear();
        filterOptions.clear();

        compField.clear();
        comp2Field.clear();
        hsnoField.clear();
        streetField.clear();
        zipcodeField.clear();
        cityField.clear();
        ilnField.clear();
        taxIdField.clear();

        eidField.clear();
        sidField.clear();
        availableField.clear();
        mobilePdaField.clear();
        trackingZipcodeField.clear();
        trackingGpsTimeField.clear();
        trackingGpsTypeField.clear();
        trackingGpsCoordinatesField.clear();
        trackingAvailableTimeField.clear();
        groupField.clear();
        groupField.setItems(groupOptions);
        filterField.clear();
        filterField.setItems(filterOptions);

        accountField.clear();
        nameField.clear();
        firstnameField.clear();
        emailField.clear();
        phoneField.clear();
        phone2Field.clear();
        faxField.clear();
        birthdateField.clear();
        countryField.clear();
        passwordField.clear();

        vehicleGrid.setItems(vehicles);
        vehicleGrid.asSingleSelect().clear();
        refreshVehicleActionState();
    }

    private VehicleTypeOption findVehicleType(Integer typeId) {
        if (typeId == null) {
            return null;
        }
        for (VehicleTypeOption option : vehicleTypes) {
            if (option.id().equals(typeId)) {
                return option;
            }
        }
        return null;
    }

    private String findVehicleTypeLabel(Integer typeId) {
        VehicleTypeOption option = findVehicleType(typeId);
        return option != null ? option.label() : "";
    }

    private LinkedHashSet<GroupOption> selectGroups(String value) {
        Set<Long> selectedIds = parseLongTokens(value);
        LinkedHashSet<GroupOption> selected = new LinkedHashSet<>();
        for (GroupOption option : groupOptions) {
            if (selectedIds.contains(option.id())) {
                selected.add(option);
            }
        }
        return selected;
    }

    private LinkedHashSet<FilterOption> selectFilters(String value) {
        Set<String> selectedCodes = parseStringTokens(value);
        LinkedHashSet<FilterOption> selected = new LinkedHashSet<>();
        for (FilterOption option : filterOptions) {
            if (selectedCodes.contains(option.shortCode())) {
                selected.add(option);
            }
        }
        return selected;
    }

    private String toGroupString(Set<GroupOption> selected) {
        if (selected == null || selected.isEmpty()) {
            return "";
        }
        return "," + selected.stream()
                .map(GroupOption::id)
                .sorted()
                .map(String::valueOf)
                .reduce((left, right) -> left + "," + right)
                .orElse("") + ",";
    }

    private String toFilterString(Set<FilterOption> selected) {
        if (selected == null || selected.isEmpty()) {
            return "";
        }
        return "," + selected.stream()
                .map(FilterOption::shortCode)
                .sorted(String::compareToIgnoreCase)
                .reduce((left, right) -> left + "," + right)
                .orElse("") + ",";
    }

    private Set<Long> parseLongTokens(String value) {
        Set<Long> result = new LinkedHashSet<>();
        if (value == null || value.isBlank()) {
            return result;
        }
        for (String token : value.split(",")) {
            if (token == null || token.isBlank()) {
                continue;
            }
            try {
                result.add(Long.parseLong(token.trim()));
            } catch (NumberFormatException ignored) {
            }
        }
        return result;
    }

    private Set<String> parseStringTokens(String value) {
        Set<String> result = new LinkedHashSet<>();
        if (value == null || value.isBlank()) {
            return result;
        }
        for (String token : value.split(",")) {
            if (token != null && !token.isBlank()) {
                result.add(token.trim());
            }
        }
        return result;
    }

    private Long toLong(Object obj) {
        if (obj == null) {
            return null;
        }
        if (obj instanceof Number number) {
            return number.longValue();
        }
        try {
            return Long.parseLong(obj.toString());
        } catch (NumberFormatException e) {
            return null;
        }
    }

    private Integer toInteger(Object obj) {
        if (obj == null) {
            return null;
        }
        if (obj instanceof Number number) {
            return number.intValue();
        }
        try {
            return Integer.parseInt(obj.toString());
        } catch (NumberFormatException e) {
            return null;
        }
    }

    private String str(Object obj) {
        return obj != null ? obj.toString() : "";
    }

    private String dateTimeText(Object obj) {
        String value = str(obj);
        return value.isBlank() ? "" : value.replace("T", " ");
    }

    private String trackingCoordinates(Map<String, Object> courier) {
        String latitude = str(courier.get("gpsLatitude"));
        String longitude = str(courier.get("gpsLongitude"));
        if (latitude.isBlank() || longitude.isBlank()) {
            return "";
        }
        return latitude + ", " + longitude;
    }

    private String gpsTypeLabel(Integer gpsType) {
        if (gpsType == null) {
            return "";
        }
        return switch (gpsType) {
            case 0 -> "unbestimmt";
            case 1 -> "LBS";
            case 2 -> "GPS";
            case 3 -> "Network";
            case 9 -> "Ortung aus";
            default -> "Typ " + gpsType;
        };
    }

    private record VehicleTypeOption(Integer id, String label) { }

    private record VehicleRow(Long id, String sid, Integer vehicleTypeId, String vehicleTypeName) { }

    private record GroupOption(Long id, String label) { }

    private record FilterOption(String shortCode, String label) { }
}
