package de.votian.web.view;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.vaadin.flow.component.Component;
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.combobox.MultiSelectComboBox;
import com.vaadin.flow.component.datepicker.DatePicker;
import com.vaadin.flow.component.datetimepicker.DateTimePicker;
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.Div;
import com.vaadin.flow.component.html.H3;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.notification.NotificationVariant;
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.tabs.Tab;
import com.vaadin.flow.component.tabs.Tabs;
import com.vaadin.flow.component.textfield.TextArea;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import de.votian.web.model.SessionData;
import de.votian.web.service.RestClientService;

import java.time.DayOfWeek;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.time.format.TextStyle;
import java.time.temporal.TemporalAdjusters;
import java.util.Arrays;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

@Route(value = "groupware", layout = MainLayout.class)
@PageTitle("Votian - Vertrieb")
public class GroupwareView extends VerticalLayout {

    private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("dd.MM.yyyy");
    private static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm");
    private static final DateTimeFormatter DATE_TIME_FORMAT = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm");

    private final RestClientService restClient;
    private final SessionData sessionData;

    private final Tab dayTab = new Tab("Tag");
    private final Tab weekTab = new Tab("Woche");
    private final Tab monthTab = new Tab("Monat");
    private final Tab tasksTab = new Tab("Aufgaben");
    private final Tabs viewTabs = new Tabs(dayTab, weekTab, monthTab, tasksTab);
    private final Map<Tab, ViewMode> modesByTab = new LinkedHashMap<>();

    private final Button previousButton = new Button("Zurueck");
    private final Button todayButton = new Button("Heute");
    private final Button nextButton = new Button("Weiter");
    private final Button reloadButton = new Button("Aktualisieren");
    private final Button createButton = new Button("Neuer Termin");
    private final DatePicker focusDateField = new DatePicker("Datum");
    private final ComboBox<ParticipantOption> userFilter = new ComboBox<>("Ersteller/Teilnehmer");
    private final ComboBox<CategoryOption> category1Filter = new ComboBox<>("Status");
    private final ComboBox<CategoryOption> category3Filter = new ComboBox<>("Terminart");
    private final ComboBox<CategoryOption> category4Filter = new ComboBox<>("Kontrolle");
    private final ComboBox<CustomerOption> customerFilter = new ComboBox<>("Kunde");
    private final Span rangeCaption = new Span();
    private final H3 detailCaption = new H3();
    private final Div calendarPanel = new Div();
    private final Grid<AppointmentRow> appointmentGrid = new Grid<>(AppointmentRow.class, false);

    private GroupwareOptionsPayload options;
    private List<AppointmentPayload> loadedAppointments = List.of();
    private LocalDate focusDate = LocalDate.now();

    public GroupwareView(RestClientService restClient, SessionData sessionData) {
        this.restClient = restClient;
        this.sessionData = sessionData;

        modesByTab.put(dayTab, ViewMode.DAY);
        modesByTab.put(weekTab, ViewMode.WEEK);
        modesByTab.put(monthTab, ViewMode.MONTH);
        modesByTab.put(tasksTab, ViewMode.TASKS);

        setSizeFull();
        setPadding(true);
        setSpacing(true);

        configureFilters();
        configureToolbar();
        configureCalendarPanel();
        configureGrid();

        add(buildPrimaryToolbar(), buildFilterBar(), rangeCaption, calendarPanel, detailCaption, appointmentGrid);
        expand(appointmentGrid);

        loadOptions();
    }

    private void configureFilters() {
        viewTabs.addSelectedChangeListener(event -> refreshAppointments());
        viewTabs.setWidth("420px");

        focusDateField.setValue(focusDate);
        focusDateField.addValueChangeListener(event -> {
            if (event.getValue() != null) {
                focusDate = event.getValue();
                refreshAppointments();
            }
        });

        previousButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
        previousButton.addClickListener(event -> shiftFocusDate(-1));

        todayButton.addThemeVariants(ButtonVariant.LUMO_CONTRAST);
        todayButton.addClickListener(event -> {
            focusDate = LocalDate.now();
            focusDateField.setValue(focusDate);
            refreshAppointments();
        });

        nextButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
        nextButton.addClickListener(event -> shiftFocusDate(1));

        reloadButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
        reloadButton.addClickListener(event -> refreshAppointments());

        createButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
        createButton.addClickListener(event -> openEditor(null));

        configureFilter(userFilter, ParticipantOption::label);
        userFilter.addValueChangeListener(event -> refreshAppointments());

        configureFilter(category1Filter, CategoryOption::label);
        category1Filter.addValueChangeListener(event -> refreshAppointments());

        configureFilter(category3Filter, CategoryOption::label);
        category3Filter.addValueChangeListener(event -> refreshAppointments());

        configureFilter(category4Filter, CategoryOption::label);
        category4Filter.addValueChangeListener(event -> refreshAppointments());

        customerFilter.setWidth("320px");
        customerFilter.setClearButtonVisible(true);
        customerFilter.setItemLabelGenerator(CustomerOption::label);
        customerFilter.addValueChangeListener(event -> refreshAppointments());
    }

    private <T> void configureFilter(ComboBox<T> comboBox, java.util.function.Function<T, String> labelProvider) {
        comboBox.setWidth("220px");
        comboBox.setClearButtonVisible(true);
        comboBox.setItemLabelGenerator(item -> item != null ? labelProvider.apply(item) : "");
    }

    private void configureToolbar() {
        rangeCaption.getStyle()
                .set("font-size", "var(--lumo-font-size-m)")
                .set("font-weight", "600");
        detailCaption.getStyle().set("margin", "0");
    }

    private HorizontalLayout buildPrimaryToolbar() {
        HorizontalLayout toolbar = new HorizontalLayout(viewTabs, previousButton, todayButton, nextButton, focusDateField, reloadButton, createButton);
        toolbar.setWidthFull();
        toolbar.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.END);
        toolbar.expand(viewTabs);
        return toolbar;
    }

    private HorizontalLayout buildFilterBar() {
        HorizontalLayout filterBar = new HorizontalLayout(userFilter, category1Filter, category3Filter, category4Filter, customerFilter);
        filterBar.setWidthFull();
        filterBar.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.END);
        filterBar.getStyle().set("flex-wrap", "wrap");
        return filterBar;
    }

    private void configureCalendarPanel() {
        calendarPanel.setWidthFull();
        calendarPanel.getStyle()
                .set("background", "linear-gradient(180deg, var(--lumo-base-color), var(--lumo-contrast-5pct))")
                .set("border", "1px solid var(--lumo-contrast-10pct)")
                .set("border-radius", "var(--lumo-border-radius-l)")
                .set("padding", "var(--lumo-space-m)");
    }

    private void configureGrid() {
        appointmentGrid.addColumn(row -> DATE_FORMAT.format(row.referenceDate())).setHeader("Datum").setAutoWidth(true);
        appointmentGrid.addColumn(row -> formatTime(row.appointment().startTime())).setHeader("Beginn").setAutoWidth(true);
        appointmentGrid.addColumn(row -> formatTime(row.appointment().endTime())).setHeader("Ende").setAutoWidth(true);
        appointmentGrid.addColumn(row -> value(row.appointment().category3Label())).setHeader("Terminart").setAutoWidth(true);
        appointmentGrid.addColumn(row -> value(row.appointment().category4Label())).setHeader("Kontrolle").setAutoWidth(true);
        appointmentGrid.addColumn(row -> value(row.appointment().customerName())).setHeader("Kunde").setAutoWidth(true);
        appointmentGrid.addColumn(row -> value(row.appointment().authorName())).setHeader("Ersteller").setAutoWidth(true);
        appointmentGrid.addColumn(AppointmentRow::participantSummary).setHeader("Teilnehmer").setFlexGrow(1);
        appointmentGrid.addColumn(AppointmentRow::text).setHeader("Text").setFlexGrow(2);
        appointmentGrid.addComponentColumn(row -> buildActionBar(row.appointment())).setHeader("Aktionen").setFlexGrow(1);
        appointmentGrid.setWidthFull();
        appointmentGrid.setMinHeight("360px");
        appointmentGrid.addItemDoubleClickListener(event -> openEditor(event.getItem().appointment()));
    }

    private void loadOptions() {
        try {
            options = restClient.get("/groupware/options", GroupwareOptionsPayload.class);
            List<ParticipantOption> participantOptions = options != null && options.participants() != null
                    ? options.participants()
                    : List.of();
            userFilter.setItems(participantOptions);
            userFilter.setValue(participantOptions.stream()
                    .filter(option -> option.userId() != null && option.userId().equals(sessionData.getUserId()))
                    .findFirst()
                    .orElse(null));

            category1Filter.setItems(options != null && options.statusOptions() != null ? options.statusOptions() : List.of());
            category3Filter.setItems(options != null && options.typeOptions() != null ? options.typeOptions() : List.of());
            category4Filter.setItems(options != null && options.reminderOptions() != null ? options.reminderOptions() : List.of());
            customerFilter.setItems(options != null && options.customers() != null ? options.customers() : List.of());

            refreshAppointments();
        } catch (Exception ex) {
            showError("Groupware-Optionen konnten nicht geladen werden: " + ex.getMessage());
        }
    }

    private void refreshAppointments() {
        LocalDate rangeStart = range().start();
        LocalDate rangeEnd = range().end();
        StringBuilder path = new StringBuilder("/groupware/appointments?from=")
                .append(rangeStart)
                .append("&to=")
                .append(rangeEnd);
        if (userFilter.getValue() != null && userFilter.getValue().userId() != null) {
            path.append("&userId=").append(userFilter.getValue().userId());
        }
        if (category1Filter.getValue() != null && category1Filter.getValue().value() != null) {
            path.append("&category1=").append(category1Filter.getValue().value());
        }
        if (category3Filter.getValue() != null && category3Filter.getValue().value() != null) {
            path.append("&category3=").append(category3Filter.getValue().value());
        }
        if (category4Filter.getValue() != null && category4Filter.getValue().value() != null) {
            path.append("&category4=").append(category4Filter.getValue().value());
        }
        if (customerFilter.getValue() != null && customerFilter.getValue().id() != null) {
            path.append("&customerId=").append(customerFilter.getValue().id());
        }

        try {
            AppointmentPayload[] response = restClient.get(path.toString(), AppointmentPayload[].class);
            loadedAppointments = response != null ? Arrays.asList(response) : List.of();
            loadedAppointments = loadedAppointments.stream()
                    .sorted(Comparator.comparing(AppointmentPayload::startTime, Comparator.nullsLast(Comparator.naturalOrder()))
                            .thenComparing(AppointmentPayload::id, Comparator.nullsLast(Comparator.naturalOrder())))
                    .toList();
            renderCalendar();
            refreshGrid();
        } catch (Exception ex) {
            loadedAppointments = List.of();
            calendarPanel.removeAll();
            appointmentGrid.setItems(List.of());
            showError("Termine konnten nicht geladen werden: " + ex.getMessage());
        }
    }

    private void renderCalendar() {
        calendarPanel.removeAll();
        rangeCaption.setText(rangeLabel());
        detailCaption.setText(detailLabel());

        if (loadedAppointments.isEmpty()) {
            Div emptyState = new Div(new Span("Keine Termine im aktuellen Ausschnitt."));
            emptyState.getStyle().set("padding", "var(--lumo-space-s)");
            calendarPanel.add(emptyState);
            return;
        }

        switch (currentViewMode()) {
            case DAY -> calendarPanel.add(buildDayPanel(focusDate));
            case WEEK -> calendarPanel.add(buildWeekPanel());
            case MONTH -> calendarPanel.add(buildMonthPanel());
            case TASKS -> calendarPanel.add(buildTasksPanel());
        }
    }

    private Component buildDayPanel(LocalDate day) {
        VerticalLayout layout = new VerticalLayout();
        layout.setPadding(false);
        layout.setSpacing(true);

        Span headline = new Span(day.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.GERMAN) + ", " + DATE_FORMAT.format(day));
        headline.getStyle().set("font-size", "var(--lumo-font-size-l)").set("font-weight", "700");
        layout.add(headline);

        for (AppointmentPayload appointment : appointmentsForDay(day)) {
            layout.add(buildAppointmentCard(appointment, day.equals(focusDate)));
        }
        return layout;
    }

    private Component buildWeekPanel() {
        Div grid = new Div();
        grid.getStyle()
                .set("display", "grid")
                .set("grid-template-columns", "repeat(auto-fit, minmax(220px, 1fr))")
                .set("gap", "var(--lumo-space-m)");

        LocalDate start = range().start();
        for (int index = 0; index < 7; index++) {
            LocalDate day = start.plusDays(index);
            VerticalLayout dayColumn = new VerticalLayout();
            dayColumn.setPadding(true);
            dayColumn.setSpacing(true);
            dayColumn.getStyle()
                    .set("background", day.equals(focusDate) ? "var(--lumo-primary-color-10pct)" : "var(--lumo-base-color)")
                    .set("border", "1px solid var(--lumo-contrast-10pct)")
                    .set("border-radius", "var(--lumo-border-radius-m)");

            dayColumn.add(new Span(day.getDayOfWeek().getDisplayName(TextStyle.SHORT, Locale.GERMAN) + " " + DATE_FORMAT.format(day)));
            List<AppointmentPayload> appointments = appointmentsForDay(day);
            if (appointments.isEmpty()) {
                dayColumn.add(new Span("Keine Termine"));
            } else {
                appointments.forEach(appointment -> dayColumn.add(buildAppointmentCard(appointment, false)));
            }
            grid.add(dayColumn);
        }
        return grid;
    }

    private Component buildMonthPanel() {
        Div monthGrid = new Div();
        monthGrid.getStyle()
                .set("display", "grid")
                .set("grid-template-columns", "repeat(7, minmax(0, 1fr))")
                .set("gap", "10px");

        for (DayOfWeek dayOfWeek : List.of(DayOfWeek.MONDAY, DayOfWeek.TUESDAY, DayOfWeek.WEDNESDAY,
                DayOfWeek.THURSDAY, DayOfWeek.FRIDAY, DayOfWeek.SATURDAY, DayOfWeek.SUNDAY)) {
            Span weekday = new Span(dayOfWeek.getDisplayName(TextStyle.SHORT, Locale.GERMAN));
            weekday.getStyle().set("font-weight", "700");
            monthGrid.add(weekday);
        }

        YearMonth currentMonth = YearMonth.from(focusDate);
        LocalDate firstCell = currentMonth.atDay(1).with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
        LocalDate lastCell = currentMonth.atEndOfMonth().with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY));

        for (LocalDate day = firstCell; !day.isAfter(lastCell); day = day.plusDays(1)) {
            Div dayCard = new Div();
            dayCard.getStyle()
                    .set("min-height", "120px")
                    .set("padding", "10px")
                    .set("border", "1px solid var(--lumo-contrast-10pct)")
                    .set("border-radius", "var(--lumo-border-radius-m)")
                    .set("background", day.equals(focusDate)
                            ? "var(--lumo-primary-color-10pct)"
                            : (day.getMonth().equals(currentMonth.getMonth()) ? "var(--lumo-base-color)" : "var(--lumo-contrast-5pct)"))
                    .set("cursor", "pointer");

            Span number = new Span(String.valueOf(day.getDayOfMonth()));
            number.getStyle()
                    .set("font-weight", "700")
                    .set("display", "block")
                    .set("margin-bottom", "8px");
            dayCard.add(number);

            List<AppointmentPayload> dailyAppointments = appointmentsForDay(day);
            if (dailyAppointments.isEmpty()) {
                dayCard.add(new Span(" "));
            } else {
                dailyAppointments.stream().limit(3).forEach(appointment -> {
                    Span chip = new Span(formatTime(appointment.startTime()) + " " + shorten(appointment.text(), 26));
                    chip.getStyle()
                            .set("display", "block")
                            .set("background", "var(--lumo-contrast-5pct)")
                            .set("border-radius", "999px")
                            .set("padding", "2px 8px")
                            .set("font-size", "var(--lumo-font-size-s)")
                            .set("margin-bottom", "6px");
                    dayCard.add(chip);
                });
                if (dailyAppointments.size() > 3) {
                    dayCard.add(new Span("+" + (dailyAppointments.size() - 3) + " weitere"));
                }
            }

            LocalDate targetDay = day;
            dayCard.addClickListener(event -> {
                focusDate = targetDay;
                focusDateField.setValue(targetDay);
                refreshGrid();
                renderCalendar();
            });
            monthGrid.add(dayCard);
        }

        return monthGrid;
    }

    private Component buildTasksPanel() {
        VerticalLayout layout = new VerticalLayout();
        layout.setPadding(false);
        layout.setSpacing(true);

        List<AppointmentPayload> tasks = taskAppointments();
        long overdueCount = tasks.stream()
                .filter(appointment -> appointment.startTime() != null && appointment.startTime().toLocalDate().isBefore(LocalDate.now()))
                .count();

        HorizontalLayout summary = new HorizontalLayout(
                buildCounter("Offene Wiedervorlagen", String.valueOf(tasks.size())),
                buildCounter("Ueberfaellig", String.valueOf(overdueCount)),
                buildCounter("Naechste Faelligkeit", tasks.isEmpty() ? "-" : DATE_TIME_FORMAT.format(tasks.getFirst().startTime()))
        );
        summary.setWidthFull();
        summary.getStyle().set("flex-wrap", "wrap");
        layout.add(summary);

        if (tasks.isEmpty()) {
            layout.add(new Span("Keine offenen Wiedervorlagen."));
        } else {
            tasks.stream().limit(8).forEach(appointment -> layout.add(buildAppointmentCard(appointment, false)));
        }
        return layout;
    }

    private Component buildCounter(String label, String value) {
        VerticalLayout card = new VerticalLayout();
        card.setPadding(true);
        card.setSpacing(false);
        card.getStyle()
                .set("border", "1px solid var(--lumo-contrast-10pct)")
                .set("border-radius", "var(--lumo-border-radius-m)")
                .set("min-width", "180px");
        Span labelSpan = new Span(label);
        Span valueSpan = new Span(value);
        valueSpan.getStyle().set("font-size", "var(--lumo-font-size-xl)").set("font-weight", "700");
        card.add(labelSpan, valueSpan);
        return card;
    }

    private Component buildAppointmentCard(AppointmentPayload appointment, boolean emphasized) {
        VerticalLayout card = new VerticalLayout();
        card.setPadding(true);
        card.setSpacing(false);
        card.getStyle()
                .set("border", "1px solid var(--lumo-contrast-10pct)")
                .set("border-radius", "var(--lumo-border-radius-m)")
                .set("background", emphasized ? "var(--lumo-primary-color-10pct)" : "var(--lumo-base-color)");

        Span time = new Span(formatTime(appointment.startTime()) + " - " + formatTime(appointment.endTime()));
        time.getStyle().set("font-weight", "700");
        Span type = new Span(value(appointment.category3Label()));
        type.getStyle().set("color", "var(--lumo-secondary-text-color)");
        Span text = new Span(value(appointment.text()));
        Span meta = new Span(value(appointment.customerName()) + (appointment.authorName() != null ? " | " + appointment.authorName() : ""));
        meta.getStyle().set("color", "var(--lumo-secondary-text-color)").set("font-size", "var(--lumo-font-size-s)");
        card.add(time, type, text, meta);
        return card;
    }

    private void refreshGrid() {
        List<AppointmentRow> rows = switch (currentViewMode()) {
            case DAY -> appointmentsForDay(focusDate).stream()
                    .map(appointment -> new AppointmentRow(focusDate, appointment))
                    .toList();
            case WEEK -> loadedAppointments.stream()
                    .map(appointment -> new AppointmentRow(resolveReferenceDate(appointment), appointment))
                    .toList();
            case MONTH -> appointmentsForDay(focusDate).stream()
                    .map(appointment -> new AppointmentRow(focusDate, appointment))
                    .toList();
            case TASKS -> taskAppointments().stream()
                    .map(appointment -> new AppointmentRow(resolveReferenceDate(appointment), appointment))
                    .toList();
        };
        appointmentGrid.setItems(rows);
    }

    private Component buildActionBar(AppointmentPayload appointment) {
        HorizontalLayout actions = new HorizontalLayout();
        actions.setSpacing(false);
        actions.getStyle().set("flex-wrap", "wrap").set("gap", "6px");

        Button editButton = compactButton("Bearbeiten", () -> openEditor(appointment));
        Button resubmitButton = compactButton("Wiedervorlage", () -> openResubmissionDialog(appointment));
        Button cancelButton = compactButton("Absagen", () -> openCancellationDialog(appointment));

        actions.add(editButton, resubmitButton, cancelButton);

        if (canConfirm(appointment)) {
            actions.add(compactButton("Bestaetigen", () -> confirmAppointment(appointment)));
        }
        if (appointment.reminderActive() && !appointment.finished()) {
            actions.add(compactButton("Erledigt", () -> finishAppointment(appointment)));
        }

        return actions;
    }

    private Button compactButton(String label, Runnable action) {
        Button button = new Button(label, event -> action.run());
        button.addThemeVariants(ButtonVariant.LUMO_TERTIARY_INLINE, ButtonVariant.LUMO_SMALL);
        return button;
    }

    private void openEditor(AppointmentPayload existing) {
        Dialog dialog = new Dialog();
        dialog.setHeaderTitle(existing == null ? "Termin anlegen" : "Termin bearbeiten");
        dialog.setWidth("960px");

        DateTimePicker startField = new DateTimePicker("Beginn");
        DateTimePicker endField = new DateTimePicker("Ende");
        startField.setStep(Duration.ofMinutes(15));
        endField.setStep(Duration.ofMinutes(15));

        ComboBox<CategoryOption> statusField = categoryBox("Status", options != null ? options.statusOptions() : List.of());
        ComboBox<CategoryOption> visibilityField = categoryBox("Sichtbarkeit", options != null ? options.visibilityOptions() : List.of());
        ComboBox<CategoryOption> typeField = categoryBox("Terminart", options != null ? options.typeOptions() : List.of());
        ComboBox<CategoryOption> controlField = categoryBox("Kontrolle", options != null ? options.reminderOptions() : List.of());

        ComboBox<CustomerOption> customerField = new ComboBox<>("Kunde");
        customerField.setWidthFull();
        customerField.setClearButtonVisible(true);
        customerField.setItemLabelGenerator(CustomerOption::label);
        customerField.setItems(options != null && options.customers() != null ? options.customers() : List.of());

        MultiSelectComboBox<ParticipantOption> participantsField = new MultiSelectComboBox<>("Teilnehmer");
        participantsField.setWidthFull();
        participantsField.setItemLabelGenerator(ParticipantOption::label);
        participantsField.setItems(options != null && options.participants() != null ? options.participants() : List.of());

        ComboBox<GroupOption> groupField = new ComboBox<>("Gruppe");
        groupField.setWidthFull();
        groupField.setItemLabelGenerator(GroupOption::name);
        groupField.setItems(groupOptions());

        Button addGroupButton = new Button("Gruppe hinzufuegen");
        addGroupButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
        addGroupButton.addClickListener(event -> {
            GroupOption selectedGroup = groupField.getValue();
            if (selectedGroup == null) {
                return;
            }
            LinkedHashSet<ParticipantOption> selectedParticipants = new LinkedHashSet<>(participantsField.getValue());
            for (Long userId : selectedGroup.memberUserIds()) {
                participantOption(userId).ifPresent(selectedParticipants::add);
            }
            participantsField.setValue(selectedParticipants);
        });

        TextArea textField = new TextArea("Text");
        textField.setWidthFull();
        textField.setMinHeight("220px");

        Checkbox notifyParticipants = new Checkbox("Teilnehmer benachrichtigen");
        TextField externalEmailsField = textField("Weitere Mailadressen");
        TextField salutationField = textField("Anrede");
        TextField greetingsField = textField("Grussformel");

        if (existing == null) {
            LocalDateTime base = LocalDateTime.of(focusDate, LocalTime.now().withMinute(0).withSecond(0).withNano(0));
            startField.setValue(base.plusHours(1));
            endField.setValue(base.plusHours(2));
            statusField.setValue(findCategory(options != null ? options.statusOptions() : List.of(), 1));
            visibilityField.setValue(findCategory(options != null ? options.visibilityOptions() : List.of(), 1));
            typeField.setValue(findCategory(options != null ? options.typeOptions() : List.of(),
                    options != null ? options.defaultTypeCategory() : 1));
            controlField.setValue(findCategory(options != null ? options.reminderOptions() : List.of(),
                    options != null ? options.defaultReminderCategory() : 0));
        } else {
            startField.setValue(existing.startTime());
            endField.setValue(existing.endTime());
            statusField.setValue(findCategory(options != null ? options.statusOptions() : List.of(), existing.category1()));
            visibilityField.setValue(findCategory(options != null ? options.visibilityOptions() : List.of(), existing.category2()));
            typeField.setValue(findCategory(options != null ? options.typeOptions() : List.of(), existing.category3()));
            controlField.setValue(findCategory(options != null ? options.reminderOptions() : List.of(), existing.category4()));
            customerField.setValue(findCustomer(existing.customerId()));
            participantsField.setValue(existing.participantUserIds() != null
                    ? existing.participantUserIds().stream().map(this::participantOption).flatMap(java.util.Optional::stream)
                    .collect(java.util.stream.Collectors.toCollection(LinkedHashSet::new))
                    : Set.of());
            textField.setValue(existing.text() != null ? existing.text() : "");
        }

        FormLayout timingLayout = new FormLayout(startField, endField, statusField, visibilityField, typeField, controlField);
        timingLayout.setResponsiveSteps(new FormLayout.ResponsiveStep("0", 1), new FormLayout.ResponsiveStep("820px", 2));

        FormLayout relationLayout = new FormLayout(customerField, groupField, addGroupButton, participantsField);
        relationLayout.setResponsiveSteps(new FormLayout.ResponsiveStep("0", 1), new FormLayout.ResponsiveStep("820px", 2));
        relationLayout.setColspan(participantsField, 2);

        FormLayout notificationLayout = new FormLayout(notifyParticipants, externalEmailsField, salutationField, greetingsField);
        notificationLayout.setResponsiveSteps(new FormLayout.ResponsiveStep("0", 1), new FormLayout.ResponsiveStep("820px", 2));
        notificationLayout.setColspan(externalEmailsField, 2);

        VerticalLayout content = new VerticalLayout(timingLayout, relationLayout, textField, notificationLayout);
        content.setPadding(false);
        content.setSpacing(true);
        dialog.add(content);

        Button cancel = new Button("Abbrechen", event -> dialog.close());
        Button save = new Button("Speichern", event -> {
            try {
                AppointmentCommand request = new AppointmentCommand(
                        startField.getValue(),
                        endField.getValue(),
                        value(statusField.getValue()),
                        value(visibilityField.getValue()),
                        value(typeField.getValue()),
                        value(controlField.getValue()),
                        customerField.getValue() != null ? customerField.getValue().id() : null,
                        textField.getValue(),
                        participantsField.getValue().stream()
                                .map(ParticipantOption::userId)
                                .filter(Objects::nonNull)
                                .toList(),
                        notifyParticipants.getValue(),
                        splitEmails(externalEmailsField.getValue()),
                        salutationField.getValue(),
                        greetingsField.getValue()
                );

                if (existing == null) {
                    restClient.post("/groupware/appointments", request, AppointmentPayload.class);
                } else {
                    restClient.put("/groupware/appointments/" + existing.id(), request);
                }
                dialog.close();
                showSuccess(existing == null ? "Termin angelegt." : "Termin gespeichert.");
                refreshAppointments();
            } catch (Exception ex) {
                showError("Termin konnte nicht gespeichert werden: " + ex.getMessage());
            }
        });
        save.addThemeVariants(ButtonVariant.LUMO_PRIMARY);

        dialog.getFooter().add(cancel, save);
        dialog.open();
    }

    private void openResubmissionDialog(AppointmentPayload appointment) {
        Dialog dialog = new Dialog();
        dialog.setHeaderTitle("Wiedervorlage");

        DateTimePicker startField = new DateTimePicker("Beginn");
        DateTimePicker endField = new DateTimePicker("Ende");
        startField.setStep(Duration.ofMinutes(15));
        endField.setStep(Duration.ofMinutes(15));

        LocalDate targetDate = focusDate != null ? focusDate : LocalDate.now();
        LocalTime startTime = appointment.startTime() != null ? appointment.startTime().toLocalTime() : LocalTime.of(9, 0);
        LocalTime endTime = appointment.endTime() != null ? appointment.endTime().toLocalTime() : startTime.plusHours(1);
        startField.setValue(LocalDateTime.of(targetDate, startTime));
        endField.setValue(LocalDateTime.of(targetDate, endTime));

        ComboBox<CategoryOption> statusField = categoryBox("Status", options != null ? options.statusOptions() : List.of());
        ComboBox<CategoryOption> visibilityField = categoryBox("Sichtbarkeit", options != null ? options.visibilityOptions() : List.of());
        ComboBox<CategoryOption> typeField = categoryBox("Terminart", options != null ? options.typeOptions() : List.of());
        ComboBox<CategoryOption> controlField = categoryBox("Kontrolle", options != null ? options.reminderOptions() : List.of());
        statusField.setValue(findCategory(options != null ? options.statusOptions() : List.of(), appointment.category1()));
        visibilityField.setValue(findCategory(options != null ? options.visibilityOptions() : List.of(), appointment.category2()));
        typeField.setValue(findCategory(options != null ? options.typeOptions() : List.of(), appointment.category3()));
        controlField.setValue(findCategory(options != null ? options.reminderOptions() : List.of(), appointment.category4()));

        FormLayout layout = new FormLayout(startField, endField, statusField, visibilityField, typeField, controlField);
        layout.setResponsiveSteps(new FormLayout.ResponsiveStep("0", 1), new FormLayout.ResponsiveStep("700px", 2));
        dialog.add(layout);

        Button cancel = new Button("Abbrechen", event -> dialog.close());
        Button save = new Button("Anlegen", event -> {
            try {
                ResubmissionCommand request = new ResubmissionCommand(
                        startField.getValue(),
                        endField.getValue(),
                        value(statusField.getValue()),
                        value(visibilityField.getValue()),
                        value(typeField.getValue()),
                        value(controlField.getValue())
                );
                restClient.post("/groupware/appointments/" + appointment.id() + "/resubmission", request, AppointmentPayload.class);
                dialog.close();
                showSuccess("Wiedervorlage angelegt.");
                refreshAppointments();
            } catch (Exception ex) {
                showError("Wiedervorlage konnte nicht angelegt werden: " + ex.getMessage());
            }
        });
        save.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
        dialog.getFooter().add(cancel, save);
        dialog.open();
    }

    private void openCancellationDialog(AppointmentPayload appointment) {
        Dialog dialog = new Dialog();
        dialog.setHeaderTitle("Termin absagen");

        Checkbox notifyParticipants = new Checkbox("Teilnehmer benachrichtigen", true);
        TextField externalEmails = textField("Weitere Mailadressen");
        VerticalLayout content = new VerticalLayout(
                new Span("Der Termin wird entfernt."),
                notifyParticipants,
                externalEmails
        );
        content.setPadding(false);
        content.setSpacing(true);
        dialog.add(content);

        Button cancel = new Button("Abbrechen", event -> dialog.close());
        Button confirm = new Button("Absagen", event -> {
            try {
                CancellationCommand request = new CancellationCommand(notifyParticipants.getValue(), splitEmails(externalEmails.getValue()));
                restClient.post("/groupware/appointments/" + appointment.id() + "/cancel", request, Void.class);
                dialog.close();
                showSuccess("Termin abgesagt.");
                refreshAppointments();
            } catch (Exception ex) {
                showError("Termin konnte nicht abgesagt werden: " + ex.getMessage());
            }
        });
        confirm.addThemeVariants(ButtonVariant.LUMO_ERROR, ButtonVariant.LUMO_PRIMARY);
        dialog.getFooter().add(cancel, confirm);
        dialog.open();
    }

    private void confirmAppointment(AppointmentPayload appointment) {
        try {
            restClient.post("/groupware/appointments/" + appointment.id() + "/confirm", Map.of(), AppointmentPayload.class);
            showSuccess("Teilnahme bestaetigt.");
            refreshAppointments();
        } catch (Exception ex) {
            showError("Teilnahme konnte nicht bestaetigt werden: " + ex.getMessage());
        }
    }

    private void finishAppointment(AppointmentPayload appointment) {
        try {
            restClient.post("/groupware/appointments/" + appointment.id() + "/finish", Map.of(), AppointmentPayload.class);
            showSuccess("Termin erledigt.");
            refreshAppointments();
        } catch (Exception ex) {
            showError("Termin konnte nicht abgeschlossen werden: " + ex.getMessage());
        }
    }

    private void shiftFocusDate(int direction) {
        focusDate = switch (currentViewMode()) {
            case DAY -> focusDate.plusDays(direction);
            case WEEK -> focusDate.plusWeeks(direction);
            case MONTH -> focusDate.plusMonths(direction);
            case TASKS -> focusDate.plusWeeks(direction);
        };
        focusDateField.setValue(focusDate);
        refreshAppointments();
    }

    private Range range() {
        return switch (currentViewMode()) {
            case DAY -> new Range(focusDate, focusDate);
            case WEEK -> new Range(
                    focusDate.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)),
                    focusDate.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY))
            );
            case MONTH -> new Range(
                    focusDate.withDayOfMonth(1),
                    focusDate.withDayOfMonth(focusDate.lengthOfMonth())
            );
            case TASKS -> new Range(
                    focusDate.minusDays(14),
                    focusDate.plusDays(60)
            );
        };
    }

    private String rangeLabel() {
        Range range = range();
        return switch (currentViewMode()) {
            case DAY -> "Tagesansicht fuer " + DATE_FORMAT.format(range.start());
            case WEEK -> "Wochenansicht " + DATE_FORMAT.format(range.start()) + " bis " + DATE_FORMAT.format(range.end());
            case MONTH -> "Monatsansicht " + focusDate.getMonth().getDisplayName(TextStyle.FULL, Locale.GERMAN) + " " + focusDate.getYear();
            case TASKS -> "Aufgabenfenster " + DATE_FORMAT.format(range.start()) + " bis " + DATE_FORMAT.format(range.end());
        };
    }

    private String detailLabel() {
        return switch (currentViewMode()) {
            case DAY -> "Terminliste";
            case WEEK -> "Alle Termine der Woche";
            case MONTH -> "Termine fuer " + DATE_FORMAT.format(focusDate);
            case TASKS -> "Offene Wiedervorlagen";
        };
    }

    private ViewMode currentViewMode() {
        return modesByTab.getOrDefault(viewTabs.getSelectedTab(), ViewMode.DAY);
    }

    private List<AppointmentPayload> appointmentsForDay(LocalDate day) {
        return loadedAppointments.stream()
                .filter(appointment -> overlapsDay(appointment, day))
                .toList();
    }

    private boolean overlapsDay(AppointmentPayload appointment, LocalDate day) {
        LocalDateTime start = appointment.startTime();
        LocalDateTime end = appointment.endTime() != null ? appointment.endTime() : appointment.startTime();
        if (start == null || end == null) {
            return false;
        }
        return !start.toLocalDate().isAfter(day) && !end.toLocalDate().isBefore(day);
    }

    private List<AppointmentPayload> taskAppointments() {
        return loadedAppointments.stream()
                .filter(appointment -> appointment.reminderActive() && !appointment.finished())
                .sorted(Comparator.comparing(AppointmentPayload::startTime, Comparator.nullsLast(Comparator.naturalOrder())))
                .toList();
    }

    private LocalDate resolveReferenceDate(AppointmentPayload appointment) {
        return appointment.startTime() != null ? appointment.startTime().toLocalDate() : focusDate;
    }

    private ComboBox<CategoryOption> categoryBox(String label, List<CategoryOption> items) {
        ComboBox<CategoryOption> box = new ComboBox<>(label);
        box.setWidthFull();
        box.setClearButtonVisible(true);
        box.setItemLabelGenerator(CategoryOption::label);
        box.setItems(items != null ? items : List.of());
        return box;
    }

    private java.util.Optional<ParticipantOption> participantOption(Long userId) {
        if (options == null || options.participants() == null || userId == null) {
            return java.util.Optional.empty();
        }
        return options.participants().stream().filter(option -> userId.equals(option.userId())).findFirst();
    }

    private CustomerOption findCustomer(Long customerId) {
        if (options == null || options.customers() == null || customerId == null) {
            return null;
        }
        return options.customers().stream().filter(customer -> customerId.equals(customer.id())).findFirst().orElse(null);
    }

    private CategoryOption findCategory(List<CategoryOption> options, Integer value) {
        if (options == null || value == null) {
            return null;
        }
        return options.stream().filter(option -> value.equals(option.value())).findFirst().orElse(null);
    }

    private List<GroupOption> groupOptions() {
        if (options == null || options.groups() == null) {
            return List.of();
        }
        return options.groups().stream()
                .map(group -> new GroupOption(group.id(), group.name(), group.memberUserIds() != null ? group.memberUserIds() : List.of()))
                .toList();
    }

    private boolean canConfirm(AppointmentPayload appointment) {
        if (sessionData.getUserId() == null || appointment.participantUserIds() == null) {
            return false;
        }
        if (!appointment.participantUserIds().contains(sessionData.getUserId())) {
            return false;
        }
        if (appointment.participants() == null) {
            return true;
        }
        return appointment.participants().stream()
                .filter(participant -> sessionData.getUserId().equals(participant.userId()))
                .noneMatch(ParticipantStatePayload::confirmed);
    }

    private String formatTime(LocalDateTime value) {
        return value != null ? TIME_FORMAT.format(value) : "-";
    }

    private String shorten(String text, int maxLength) {
        if (text == null || text.length() <= maxLength) {
            return value(text);
        }
        return text.substring(0, maxLength - 3) + "...";
    }

    private String value(String text) {
        return text != null && !text.isBlank() ? text : "-";
    }

    private Integer value(CategoryOption option) {
        return option != null ? option.value() : null;
    }

    private List<String> splitEmails(String rawValue) {
        if (rawValue == null || rawValue.isBlank()) {
            return List.of();
        }
        return Arrays.stream(rawValue.split("[,;\\n\\r]+"))
                .map(String::trim)
                .filter(token -> !token.isBlank())
                .distinct()
                .toList();
    }

    private TextField textField(String label) {
        TextField field = new TextField(label);
        field.setWidthFull();
        return field;
    }

    private void showSuccess(String message) {
        Notification notification = Notification.show(message, 3000, Notification.Position.MIDDLE);
        notification.addThemeVariants(NotificationVariant.LUMO_SUCCESS);
    }

    private void showError(String message) {
        Notification notification = Notification.show(message, 5000, Notification.Position.MIDDLE);
        notification.addThemeVariants(NotificationVariant.LUMO_ERROR);
    }

    private enum ViewMode {
        DAY,
        WEEK,
        MONTH,
        TASKS
    }

    private record Range(LocalDate start, LocalDate end) {
    }

    private record AppointmentRow(LocalDate referenceDate, AppointmentPayload appointment) {
        String participantSummary() {
            if (appointment.participants() == null || appointment.participants().isEmpty()) {
                return "-";
            }
            return appointment.participants().stream()
                    .map(ParticipantStatePayload::name)
                    .filter(Objects::nonNull)
                    .sorted(String::compareToIgnoreCase)
                    .reduce((left, right) -> left + ", " + right)
                    .orElse("-");
        }

        String text() {
            return appointment.text() != null ? appointment.text() : "";
        }
    }

    private record GroupOption(Long id, String name, List<Long> memberUserIds) {
    }

    @JsonIgnoreProperties(ignoreUnknown = true)
    private record GroupwareOptionsPayload(List<CategoryOption> statusOptions,
                                           List<CategoryOption> visibilityOptions,
                                           List<CategoryOption> typeOptions,
                                           List<CategoryOption> reminderOptions,
                                           List<ParticipantOption> participants,
                                           List<GroupPayload> groups,
                                           List<CustomerOption> customers,
                                           Integer defaultTypeCategory,
                                           Integer defaultReminderCategory) {
    }

    @JsonIgnoreProperties(ignoreUnknown = true)
    private record CategoryOption(Integer value, String label) {
    }

    @JsonIgnoreProperties(ignoreUnknown = true)
    private record ParticipantOption(Long userId,
                                     Long employeeId,
                                     String name,
                                     String email,
                                     String headquartersMnemonic,
                                     List<Long> groupIds) {
        String label() {
            return name != null ? name : String.valueOf(userId);
        }
    }

    @JsonIgnoreProperties(ignoreUnknown = true)
    private record GroupPayload(Long id, String name, List<Long> memberUserIds) {
    }

    @JsonIgnoreProperties(ignoreUnknown = true)
    private record CustomerOption(Long id, String eid, String companyName) {
        String label() {
            String prefix = eid != null && !eid.isBlank() ? eid + " - " : "";
            return prefix + (companyName != null ? companyName : "");
        }
    }

    @JsonIgnoreProperties(ignoreUnknown = true)
    private record ParticipantStatePayload(Long userId, String name, String email, boolean confirmed) {
    }

    @JsonIgnoreProperties(ignoreUnknown = true)
    private record AppointmentPayload(Long id,
                                      Long headquartersId,
                                      Long authorUserId,
                                      String authorName,
                                      Long customerId,
                                      String customerEid,
                                      String customerName,
                                      Integer category1,
                                      Integer category2,
                                      Integer category3,
                                      Integer category4,
                                      String category1Label,
                                      String category2Label,
                                      String category3Label,
                                      String category4Label,
                                      String text,
                                      LocalDateTime startTime,
                                      LocalDateTime endTime,
                                      boolean reminderActive,
                                      boolean finished,
                                      List<Long> participantUserIds,
                                      List<ParticipantStatePayload> participants) {
    }

    private record AppointmentCommand(LocalDateTime startTime,
                                      LocalDateTime endTime,
                                      Integer category1,
                                      Integer category2,
                                      Integer category3,
                                      Integer category4,
                                      Long customerId,
                                      String text,
                                      List<Long> participantUserIds,
                                      Boolean notifyParticipants,
                                      List<String> externalEmails,
                                      String mailSalutation,
                                      String mailGreetings) {
    }

    private record ResubmissionCommand(LocalDateTime startTime,
                                       LocalDateTime endTime,
                                       Integer category1,
                                       Integer category2,
                                       Integer category3,
                                       Integer category4) {
    }

    private record CancellationCommand(Boolean notifyParticipants, List<String> externalEmails) {
    }
}
