package de.gecheckt.pdf.umbenenner.adapter.in.gui.history;

import java.nio.file.Path;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.BooleanSupplier;
import java.util.function.Supplier;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import de.gecheckt.pdf.umbenenner.application.port.out.DocumentRecord;
import de.gecheckt.pdf.umbenenner.application.port.out.ProcessingAttempt;
import de.gecheckt.pdf.umbenenner.application.port.out.history.DocumentHistoryRow;
import de.gecheckt.pdf.umbenenner.application.port.out.history.HistoryQuery;
import de.gecheckt.pdf.umbenenner.application.usecase.DefaultHistoryDetailsUseCase.HistoryDetailsResult;
import de.gecheckt.pdf.umbenenner.application.usecase.DefaultHistoryOverviewUseCase.HistoryOverviewResult;
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
import de.gecheckt.pdf.umbenenner.domain.model.ProcessingStatus;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.SplitPane;
import javafx.scene.control.Tab;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;

/**
 * Dritter Haupt-Tab des JavaFX-Editorfensters: der Historien-Tab „Verlauf".
 * <p>
 * Zeigt alle jemals verarbeiteten Dokumente aus der SQLite-Datenbank in einer
 * zweispaltigen Ansicht: links eine filterbare Dokumentenliste (~55%),
 * rechts ein Detailbereich mit Stammsatz, Versuchstabelle und KI-Begründung (~45%).
 *
 * <h2>Layout</h2>
 * <pre>
 *   ┌─────────────────────────────────────────────────────────────────┐
 *   │ [ Suchfeld                   ] [ Status ▾ ] [ Aktualisieren ]   │
 *   ├────────────────────────┬────────────────────────────────────────┤
 *   │ Dokumentenliste (~55%) │ Detailbereich (~45%)                   │
 *   │                        │   Dokument-Info                        │
 *   │                        │   Versuche-Tabelle                     │
 *   │                        │   KI-Begründung                        │
 *   ├────────────────────────┴────────────────────────────────────────┤
 *   │ [ Status zurücksetzen ] [ Eintrag löschen ]   Statuszeile       │
 *   └─────────────────────────────────────────────────────────────────┘
 * </pre>
 *
 * <h2>Threading</h2>
 * <p>Alle DB-Zugriffe laufen auf einem Hintergrund-Worker-Thread.
 * UI-Updates erfolgen ausschließlich via {@code Platform.runLater()}.
 * Destruktive Aktionen (Reset, Löschen) sind während eines aktiven
 * Verarbeitungslaufs deaktiviert.
 */
public final class GuiHistoryTab {

    private static final Logger LOG = LogManager.getLogger(GuiHistoryTab.class);

    private static final String TAB_TITLE = "Verlauf";
    private static final String EMPTY_DB_TEXT = "Noch keine Verarbeitungen vorhanden.";
    private static final String TOO_MANY_RESULTS_TEXT =
            "Weitere Einträge vorhanden – Filter verwenden um die Trefferliste einzuschränken.";
    private static final String DETAIL_PLACEHOLDER = "Dokument auswählen für Details";
    private static final String NO_REASONING_TEXT = "Kein KI-Reasoning für diesen Versuch vorhanden.";
    private static final String LOADING_TEXT = "Wird geladen …";
    private static final String LAUF_AKTIV_HINWEIS = "Aktion während Verarbeitungslauf nicht möglich.";

    private static final DateTimeFormatter TIMESTAMP_FMT =
            DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm").withZone(ZoneId.systemDefault());

    // ---- Bridge-Ports ---------------------------------------------------
    private final GuiHistoryOverviewPort overviewPort;
    private final GuiHistoryDetailsPort detailsPort;
    private final GuiHistoryResetDocumentStatusPort resetPort;
    private final GuiDeleteDocumentHistoryPort deletePort;
    private final BooleanSupplier runningCheck;
    /** Liefert den Pfad zur aktuell geladenen Konfigurationsdatei, oder {@code null} wenn keine geladen. */
    private final Supplier<Path> configPathSupplier;

    // ---- JavaFX-Knoten --------------------------------------------------
    private final Tab tab = new Tab(TAB_TITLE);

    private final TextField searchField = new TextField();
    private final ComboBox<String> statusFilterBox = new ComboBox<>();
    private final Button refreshButton = new Button("Aktualisieren");

    private final TableView<DocumentHistoryRow> overviewTable = new TableView<>();
    private final ObservableList<DocumentHistoryRow> overviewItems = FXCollections.observableArrayList();

    private final Label statusBarLabel = new Label();
    private final Label moreThanMaxLabel = new Label();

    // Detailbereich
    private final GridPane detailGrid = new GridPane();
    private final Label detailFingerprintLabel = new Label();
    private final Label detailSourceFileLabel = new Label();
    private final Label detailSourcePathLabel = new Label();
    private final Label detailStatusLabel = new Label();
    private final Label detailCreatedLabel = new Label();
    private final Label detailUpdatedLabel = new Label();

    private final TableView<ProcessingAttempt> attemptsTable = new TableView<>();
    private final ObservableList<ProcessingAttempt> attemptsItems = FXCollections.observableArrayList();
    private final TextArea reasoningArea = new TextArea();

    private final Button resetButton = new Button("Status zurücksetzen");
    private final Button deleteButton = new Button("Eintrag löschen");

    // ---- Zustand --------------------------------------------------------
    private final ExecutorService workerPool;

    /**
     * Erzeugt den Historien-Tab.
     *
     * @param overviewPort       Brücke zur Dokumentenübersicht; darf nicht {@code null} sein
     * @param detailsPort        Brücke zur Detailansicht; darf nicht {@code null} sein
     * @param resetPort          Brücke zum feldgenauen Status-Reset; darf nicht {@code null} sein
     * @param deletePort         Brücke zum vollständigen Löschen; darf nicht {@code null} sein
     * @param runningCheck       Liefert {@code true} wenn gerade ein Verarbeitungslauf aktiv ist;
     *                           darf nicht {@code null} sein
     * @param configPathSupplier Liefert den Pfad zur aktuell geladenen Konfigurationsdatei,
     *                           oder {@code null} wenn keine geladen ist; darf nicht {@code null} sein
     */
    public GuiHistoryTab(
            GuiHistoryOverviewPort overviewPort,
            GuiHistoryDetailsPort detailsPort,
            GuiHistoryResetDocumentStatusPort resetPort,
            GuiDeleteDocumentHistoryPort deletePort,
            BooleanSupplier runningCheck,
            Supplier<Path> configPathSupplier) {
        this.overviewPort = Objects.requireNonNull(overviewPort, "overviewPort darf nicht null sein");
        this.detailsPort = Objects.requireNonNull(detailsPort, "detailsPort darf nicht null sein");
        this.resetPort = Objects.requireNonNull(resetPort, "resetPort darf nicht null sein");
        this.deletePort = Objects.requireNonNull(deletePort, "deletePort darf nicht null sein");
        this.runningCheck = Objects.requireNonNull(runningCheck, "runningCheck darf nicht null sein");
        this.configPathSupplier = Objects.requireNonNull(configPathSupplier, "configPathSupplier darf nicht null sein");

        this.workerPool = Executors.newSingleThreadExecutor(r -> {
            Thread t = new Thread(r, "HistoryTabWorker");
            t.setDaemon(true);
            return t;
        });

        buildUi();
        wireEvents();
        tab.setClosable(false);
    }

    /**
     * Liefert den JavaFX-{@link Tab}, der in die TabPane eingefügt werden kann.
     *
     * @return der Tab; nie {@code null}
     */
    public Tab tab() {
        return tab;
    }

    /**
     * Lädt die Dokumentenübersicht neu – muss auf dem JavaFX Application Thread aufgerufen werden.
     * Wird vom Tab-Wechsel-Listener ausgelöst.
     */
    public void refresh() {
        loadOverview();
    }

    // =========================================================================
    // UI-Aufbau
    // =========================================================================

    private void buildUi() {
        // --- Toolbar ---
        searchField.setPromptText("Suche nach Dateiname …");
        searchField.setPrefWidth(300);
        Tooltip.install(searchField, new Tooltip(
                "Freitextsuche über Quell- und Zieldateiname (Groß-/Kleinschreibung egal)."));

        statusFilterBox.getItems().add("Alle Status");
        for (ProcessingStatus s : ProcessingStatus.values()) {
            statusFilterBox.getItems().add(s.name());
        }
        statusFilterBox.getSelectionModel().selectFirst();
        Tooltip.install(statusFilterBox, new Tooltip("Status-Filter: nur Einträge mit diesem Status anzeigen."));

        refreshButton.setTooltip(new Tooltip("Dokumentenliste neu aus der Datenbank laden."));

        Region spacer = new Region();
        HBox.setHgrow(spacer, Priority.ALWAYS);

        HBox toolbar = new HBox(8, searchField, statusFilterBox, spacer, refreshButton);
        toolbar.setAlignment(Pos.CENTER_LEFT);
        toolbar.setPadding(new Insets(6, 8, 6, 8));

        // --- Dokumentenliste (links) ---
        buildOverviewTable();

        moreThanMaxLabel.setStyle("-fx-text-fill: #d98200; -fx-font-style: italic;");
        moreThanMaxLabel.setVisible(false);
        moreThanMaxLabel.setManaged(false);

        VBox leftPane = new VBox(4, overviewTable, moreThanMaxLabel);
        VBox.setVgrow(overviewTable, Priority.ALWAYS);
        leftPane.setPadding(new Insets(0, 4, 0, 0));

        // --- Detailbereich (rechts) ---
        VBox rightPane = buildDetailPane();

        // --- SplitPane ---
        SplitPane splitPane = new SplitPane(leftPane, rightPane);
        splitPane.setDividerPositions(0.55);

        // --- Aktionsleiste unten ---
        resetButton.setTooltip(new Tooltip(
                "Setzt Status, Fehlerzähler und letzten Fehlerzeitpunkt zurück. "
                + "Versuche bleiben erhalten. Das Dokument wird beim nächsten Lauf erneut verarbeitet."));
        deleteButton.setTooltip(new Tooltip(
                "Löscht den Eintrag und alle Versuche vollständig. "
                + "Diese Aktion ist nicht rückgängig zu machen."));
        resetButton.setDisable(true);
        deleteButton.setDisable(true);

        statusBarLabel.setStyle("-fx-text-fill: #555555; -fx-font-style: italic;");

        HBox actionBar = new HBox(8, resetButton, deleteButton, spacerNew(), statusBarLabel);
        actionBar.setAlignment(Pos.CENTER_LEFT);
        actionBar.setPadding(new Insets(6, 8, 6, 8));

        // --- Gesamtlayout ---
        BorderPane content = new BorderPane();
        content.setTop(toolbar);
        content.setCenter(splitPane);
        content.setBottom(actionBar);
        BorderPane.setMargin(toolbar, Insets.EMPTY);

        tab.setContent(content);
    }

    private void buildOverviewTable() {
        overviewTable.setItems(overviewItems);
        overviewTable.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
        overviewTable.setPlaceholder(new Label(EMPTY_DB_TEXT));
        overviewTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY_FLEX_LAST_COLUMN);

        // Status-Icon-Spalte
        TableColumn<DocumentHistoryRow, String> statusCol = new TableColumn<>("Status");
        statusCol.setCellValueFactory(cell ->
                new SimpleStringProperty(statusIcon(cell.getValue().overallStatus())));
        statusCol.setCellFactory(col -> new TableCell<>() {
            @Override
            protected void updateItem(String icon, boolean empty) {
                super.updateItem(icon, empty);
                if (empty || icon == null) {
                    setText(null);
                    setTooltip(null);
                } else {
                    setText(icon);
                    DocumentHistoryRow row = getTableView().getItems().get(getIndex());
                    setStyle("-fx-text-fill: " + statusColor(row.overallStatus()) + "; -fx-font-weight: bold;");
                    setTooltip(new Tooltip(statusTooltip(row.overallStatus())));
                }
            }
        });
        statusCol.setPrefWidth(60);
        statusCol.setMaxWidth(70);

        // Quelldateiname
        TableColumn<DocumentHistoryRow, String> sourceCol = new TableColumn<>("Quelldatei");
        sourceCol.setCellValueFactory(cell ->
                new SimpleStringProperty(cell.getValue().sourceFileName()));
        sourceCol.setCellFactory(col -> ellipsisCell());

        // Zieldateiname
        TableColumn<DocumentHistoryRow, String> targetCol = new TableColumn<>("Zieldatei");
        targetCol.setCellValueFactory(cell ->
                new SimpleStringProperty(
                        cell.getValue().targetFileName() != null ? cell.getValue().targetFileName() : "—"));
        targetCol.setCellFactory(col -> ellipsisCell());

        // Letzter Versuch
        TableColumn<DocumentHistoryRow, String> updatedCol = new TableColumn<>("Letzter Versuch");
        updatedCol.setCellValueFactory(cell ->
                new SimpleStringProperty(formatInstant(cell.getValue().updatedAt())));
        updatedCol.setPrefWidth(140);
        updatedCol.setMaxWidth(160);

        // Anzahl Versuche
        TableColumn<DocumentHistoryRow, String> countCol = new TableColumn<>("Versuche");
        countCol.setCellValueFactory(cell ->
                new SimpleStringProperty(String.valueOf(cell.getValue().attemptCount())));
        countCol.setPrefWidth(70);
        countCol.setMaxWidth(80);

        overviewTable.getColumns().setAll(statusCol, sourceCol, targetCol, updatedCol, countCol);
    }

    private VBox buildDetailPane() {
        // Dokument-Info
        detailGrid.setHgap(8);
        detailGrid.setVgap(4);
        detailGrid.setPadding(new Insets(8));

        addDetailRow(0, "Fingerprint:", detailFingerprintLabel);
        addDetailRow(1, "Quelldatei:", detailSourceFileLabel);
        addDetailRow(2, "Quellpfad:", detailSourcePathLabel);
        addDetailRow(3, "Status:", detailStatusLabel);
        addDetailRow(4, "Erstellt:", detailCreatedLabel);
        addDetailRow(5, "Aktualisiert:", detailUpdatedLabel);

        Label detailTitle = new Label("Dokument-Details");
        detailTitle.setStyle("-fx-font-weight: bold;");

        // Versuche-Tabelle
        buildAttemptsTable();
        Label attemptsTitle = new Label("Verarbeitungsversuche");
        attemptsTitle.setStyle("-fx-font-weight: bold;");

        // KI-Begründung
        reasoningArea.setEditable(false);
        reasoningArea.setWrapText(true);
        reasoningArea.setPrefRowCount(4);
        reasoningArea.setText(DETAIL_PLACEHOLDER);
        Label reasoningTitle = new Label("KI-Begründung (ausgewählter Versuch)");
        reasoningTitle.setStyle("-fx-font-weight: bold;");

        VBox rightPane = new VBox(8,
                detailTitle, detailGrid,
                attemptsTitle, attemptsTable,
                reasoningTitle, reasoningArea);
        rightPane.setPadding(new Insets(4, 8, 4, 4));
        VBox.setVgrow(attemptsTable, Priority.ALWAYS);

        ScrollPane scroll = new ScrollPane(rightPane);
        scroll.setFitToWidth(true);
        scroll.setFitToHeight(true);

        VBox wrapper = new VBox(scroll);
        VBox.setVgrow(scroll, Priority.ALWAYS);
        return wrapper;
    }

    private void buildAttemptsTable() {
        attemptsTable.setItems(attemptsItems);
        attemptsTable.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
        attemptsTable.setPlaceholder(new Label("Keine Versuche vorhanden."));
        attemptsTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY_FLEX_LAST_COLUMN);
        attemptsTable.setPrefHeight(150);

        TableColumn<ProcessingAttempt, String> numCol = new TableColumn<>("#");
        numCol.setCellValueFactory(c ->
                new SimpleStringProperty(String.valueOf(c.getValue().attemptNumber())));
        numCol.setPrefWidth(40);
        numCol.setMaxWidth(50);

        TableColumn<ProcessingAttempt, String> dateCol = new TableColumn<>("Datum");
        dateCol.setCellValueFactory(c ->
                new SimpleStringProperty(formatInstant(c.getValue().endedAt())));
        dateCol.setPrefWidth(130);
        dateCol.setMaxWidth(150);

        TableColumn<ProcessingAttempt, String> statusCol = new TableColumn<>("Status");
        statusCol.setCellValueFactory(c ->
                new SimpleStringProperty(
                        statusIcon(c.getValue().status()) + " " + c.getValue().status().name()));
        statusCol.setPrefWidth(140);

        TableColumn<ProcessingAttempt, String> providerCol = new TableColumn<>("Provider");
        providerCol.setCellValueFactory(c ->
                new SimpleStringProperty(
                        c.getValue().aiProvider() != null ? c.getValue().aiProvider() : "—"));
        providerCol.setPrefWidth(90);

        TableColumn<ProcessingAttempt, String> modelCol = new TableColumn<>("Modell");
        modelCol.setCellValueFactory(c ->
                new SimpleStringProperty(
                        c.getValue().modelName() != null ? c.getValue().modelName() : "—"));
        modelCol.setCellFactory(col -> ellipsisCell());

        TableColumn<ProcessingAttempt, String> fileNameCol = new TableColumn<>("Vorgeschlagener Name");
        fileNameCol.setCellValueFactory(c ->
                new SimpleStringProperty(
                        c.getValue().finalTargetFileName() != null
                                ? c.getValue().finalTargetFileName() : "—"));
        fileNameCol.setCellFactory(col -> ellipsisCell());

        attemptsTable.getColumns().setAll(numCol, dateCol, statusCol, providerCol, modelCol, fileNameCol);
    }

    // =========================================================================
    // Event-Verdrahtung
    // =========================================================================

    private void wireEvents() {
        refreshButton.setOnAction(e -> loadOverview());

        // Debounce-artige Aktualisierung bei Texteingabe: direkte Suche bei Enter,
        // sonst über Fokus-Verlust oder expliziten Aktualisieren-Button
        searchField.setOnAction(e -> loadOverview());

        statusFilterBox.setOnAction(e -> loadOverview());

        // Detailbereich bei Zeilenselektion
        overviewTable.getSelectionModel().selectedItemProperty().addListener(
                (obs, old, selected) -> {
                    if (selected == null) {
                        clearDetailPane();
                        resetButton.setDisable(true);
                        deleteButton.setDisable(true);
                    } else {
                        resetButton.setDisable(runningCheck.getAsBoolean());
                        deleteButton.setDisable(runningCheck.getAsBoolean());
                        loadDetails(selected.fingerprint());
                    }
                });

        resetButton.setOnAction(e -> handleResetAction());
        deleteButton.setOnAction(e -> handleDeleteAction());

        // Tab soll beim ersten Betreten automatisch laden
        tab.selectedProperty().addListener((obs, oldVal, selected) -> {
            if (Boolean.TRUE.equals(selected)) {
                loadOverview();
            }
        });
    }

    // =========================================================================
    // Daten laden (Worker-Thread)
    // =========================================================================

    private void loadOverview() {
        statusBarLabel.setText(LOADING_TEXT);
        overviewItems.clear();
        moreThanMaxLabel.setVisible(false);
        moreThanMaxLabel.setManaged(false);

        Path configPath = configPathSupplier.get();
        if (configPath == null) {
            statusBarLabel.setText("Keine Konfiguration geladen – bitte zuerst eine Konfigurationsdatei öffnen.");
            overviewTable.setPlaceholder(new Label("Keine Konfiguration geladen."));
            return;
        }

        String searchText = searchField.getText();
        String selectedStatus = statusFilterBox.getSelectionModel().getSelectedItem();
        String statusFilter = (selectedStatus == null || "Alle Status".equals(selectedStatus))
                ? null : selectedStatus;

        HistoryQuery query = new HistoryQuery(searchText, statusFilter, HistoryQuery.DEFAULT_LIMIT);

        workerPool.submit(() -> {
            try {
                HistoryOverviewResult result = overviewPort.loadOverview(configPath, query);
                Platform.runLater(() -> {
                    overviewItems.setAll(result.rows());
                    if (result.hasMore()) {
                        moreThanMaxLabel.setText(TOO_MANY_RESULTS_TEXT);
                        moreThanMaxLabel.setVisible(true);
                        moreThanMaxLabel.setManaged(true);
                    } else {
                        moreThanMaxLabel.setVisible(false);
                        moreThanMaxLabel.setManaged(false);
                    }
                    if (result.rows().isEmpty()) {
                        overviewTable.setPlaceholder(new Label(EMPTY_DB_TEXT));
                        statusBarLabel.setText("Keine Einträge gefunden.");
                    } else {
                        statusBarLabel.setText(result.rows().size() + " Einträge geladen.");
                    }
                });
            } catch (Exception ex) {
                LOG.error("Fehler beim Laden der Historienübersicht: {}", ex.getMessage(), ex);
                Platform.runLater(() ->
                        statusBarLabel.setText("Fehler beim Laden: " + ex.getMessage()));
            }
        });
    }

    private void loadDetails(DocumentFingerprint fingerprint) {
        reasoningArea.setText(LOADING_TEXT);
        attemptsItems.clear();
        clearDetailFields();

        Path configPath = configPathSupplier.get();
        if (configPath == null) {
            reasoningArea.setText(DETAIL_PLACEHOLDER);
            return;
        }

        workerPool.submit(() -> {
            try {
                Optional<HistoryDetailsResult> result = detailsPort.loadDetails(configPath, fingerprint);
                Platform.runLater(() -> {
                    if (result.isEmpty()) {
                        clearDetailPane();
                        statusBarLabel.setText("Eintrag nicht mehr vorhanden.");
                    } else {
                        populateDetailPane(result.get());
                    }
                });
            } catch (Exception ex) {
                LOG.error("Fehler beim Laden der Dokumentdetails für {}: {}",
                        fingerprint.sha256Hex(), ex.getMessage(), ex);
                Platform.runLater(() -> {
                    reasoningArea.setText("Fehler beim Laden der Details: " + ex.getMessage());
                    statusBarLabel.setText("Fehler beim Laden der Details.");
                });
            }
        });
    }

    // =========================================================================
    // Aktionen
    // =========================================================================

    private void handleResetAction() {
        if (runningCheck.getAsBoolean()) {
            showInfo(LAUF_AKTIV_HINWEIS);
            return;
        }

        DocumentHistoryRow selected = overviewTable.getSelectionModel().getSelectedItem();
        if (selected == null) return;

        Alert confirm = new Alert(Alert.AlertType.CONFIRMATION);
        confirm.setTitle("Status zurücksetzen");
        confirm.setHeaderText("Status zurücksetzen?");
        confirm.setContentText(
                "Setzt den Status des Dokuments auf READY_FOR_AI zurück.\n"
                + "Fehlerzähler und letzter Fehlerzeitpunkt werden gelöscht.\n"
                + "Die Versuchshistorie bleibt vollständig erhalten.\n\n"
                + "Das Dokument wird beim nächsten Verarbeitungslauf erneut verarbeitet.\n\n"
                + "Quelldatei: " + selected.sourceFileName());
        Optional<ButtonType> choice = confirm.showAndWait();
        if (choice.isEmpty() || choice.get() != ButtonType.OK) return;

        DocumentFingerprint fp = selected.fingerprint();
        Path configPath = configPathSupplier.get();
        if (configPath == null) {
            showInfo("Keine Konfiguration geladen.");
            return;
        }
        resetButton.setDisable(true);
        deleteButton.setDisable(true);
        statusBarLabel.setText("Status wird zurückgesetzt …");

        workerPool.submit(() -> {
            try {
                resetPort.resetStatus(configPath, fp);
                LOG.info("Status-Reset durchgeführt für Fingerprint: {}", fp.sha256Hex());
                Platform.runLater(() -> {
                    statusBarLabel.setText("Status erfolgreich zurückgesetzt.");
                    loadOverview();
                });
            } catch (Exception ex) {
                LOG.error("Status-Reset fehlgeschlagen für {}: {}", fp.sha256Hex(), ex.getMessage(), ex);
                Platform.runLater(() -> {
                    statusBarLabel.setText("Fehler beim Status-Reset: " + ex.getMessage());
                    resetButton.setDisable(false);
                    deleteButton.setDisable(false);
                });
            }
        });
    }

    private void handleDeleteAction() {
        if (runningCheck.getAsBoolean()) {
            showInfo(LAUF_AKTIV_HINWEIS);
            return;
        }

        DocumentHistoryRow selected = overviewTable.getSelectionModel().getSelectedItem();
        if (selected == null) return;

        Alert confirm = new Alert(Alert.AlertType.WARNING);
        confirm.setTitle("Eintrag löschen");
        confirm.setHeaderText("Eintrag vollständig löschen?");
        confirm.setContentText(
                "Der Stammsatz und ALLE Verarbeitungsversuche werden unwiderruflich gelöscht.\n"
                + "Diese Aktion kann nicht rückgängig gemacht werden.\n\n"
                + "Quelldatei: " + selected.sourceFileName());
        confirm.getButtonTypes().setAll(ButtonType.OK, ButtonType.CANCEL);
        Optional<ButtonType> choice = confirm.showAndWait();
        if (choice.isEmpty() || choice.get() != ButtonType.OK) return;

        DocumentFingerprint fp = selected.fingerprint();
        Path configPath = configPathSupplier.get();
        if (configPath == null) {
            showInfo("Keine Konfiguration geladen.");
            return;
        }
        resetButton.setDisable(true);
        deleteButton.setDisable(true);
        statusBarLabel.setText("Eintrag wird gelöscht …");

        workerPool.submit(() -> {
            try {
                deletePort.deleteHistory(configPath, fp);
                LOG.info("Dokumenteintrag gelöscht für Fingerprint: {}", fp.sha256Hex());
                Platform.runLater(() -> {
                    statusBarLabel.setText("Eintrag erfolgreich gelöscht.");
                    clearDetailPane();
                    loadOverview();
                });
            } catch (Exception ex) {
                LOG.error("Löschen fehlgeschlagen für {}: {}", fp.sha256Hex(), ex.getMessage(), ex);
                Platform.runLater(() -> {
                    statusBarLabel.setText("Fehler beim Löschen: " + ex.getMessage());
                    resetButton.setDisable(false);
                    deleteButton.setDisable(false);
                });
            }
        });
    }

    // =========================================================================
    // Detail-Bereich befüllen / leeren
    // =========================================================================

    private void populateDetailPane(HistoryDetailsResult result) {
        DocumentRecord record = result.record();
        String fpFull = record.fingerprint().sha256Hex();
        detailFingerprintLabel.setText(fpFull.substring(0, Math.min(12, fpFull.length())) + " …");
        detailFingerprintLabel.setTooltip(new Tooltip(fpFull));
        detailSourceFileLabel.setText(record.lastKnownSourceFileName());
        detailSourcePathLabel.setText(record.lastKnownSourceLocator().value());
        detailSourcePathLabel.setTooltip(new Tooltip(record.lastKnownSourceLocator().value()));
        String icon = statusIcon(record.overallStatus());
        detailStatusLabel.setText(icon + " " + record.overallStatus().name());
        detailStatusLabel.setStyle("-fx-text-fill: " + statusColor(record.overallStatus()) + ";");
        detailStatusLabel.setTooltip(new Tooltip(statusTooltip(record.overallStatus())));
        detailCreatedLabel.setText(formatInstant(record.createdAt()));
        detailUpdatedLabel.setText(formatInstant(record.updatedAt()));

        attemptsItems.setAll(result.attempts());

        // Neuesten Versuch selektieren und Begründung anzeigen
        if (!result.attempts().isEmpty()) {
            ProcessingAttempt last = result.attempts().get(result.attempts().size() - 1);
            attemptsTable.getSelectionModel().select(last);
            showReasoning(last);
        } else {
            reasoningArea.setText(NO_REASONING_TEXT);
        }

        // KI-Begründung bei Versuchs-Selektion aktualisieren
        attemptsTable.getSelectionModel().selectedItemProperty().addListener(
                (obs, old, attempt) -> {
                    if (attempt != null) {
                        showReasoning(attempt);
                    }
                });
    }

    private void showReasoning(ProcessingAttempt attempt) {
        String reasoning = attempt.aiReasoning();
        reasoningArea.setText(reasoning != null && !reasoning.isBlank()
                ? reasoning : NO_REASONING_TEXT);
    }

    private void clearDetailPane() {
        clearDetailFields();
        attemptsItems.clear();
        reasoningArea.setText(DETAIL_PLACEHOLDER);
    }

    private void clearDetailFields() {
        detailFingerprintLabel.setText("");
        detailFingerprintLabel.setTooltip(null);
        detailSourceFileLabel.setText("");
        detailSourcePathLabel.setText("");
        detailSourcePathLabel.setTooltip(null);
        detailStatusLabel.setText("");
        detailStatusLabel.setStyle("");
        detailStatusLabel.setTooltip(null);
        detailCreatedLabel.setText("");
        detailUpdatedLabel.setText("");
    }

    // =========================================================================
    // Hilfsmethoden
    // =========================================================================

    private void addDetailRow(int row, String labelText, Label valueLabel) {
        Label label = new Label(labelText);
        label.setStyle("-fx-font-weight: bold;");
        valueLabel.setMaxWidth(Double.MAX_VALUE);
        GridPane.setHgrow(valueLabel, Priority.ALWAYS);
        detailGrid.add(label, 0, row);
        detailGrid.add(valueLabel, 1, row);
    }

    private String formatInstant(Instant instant) {
        if (instant == null) return "—";
        return TIMESTAMP_FMT.format(instant);
    }

    private static String statusIcon(ProcessingStatus status) {
        if (status == null) return "?";
        return switch (status) {
            case SUCCESS -> "✓";
            case FAILED_RETRYABLE -> "↻";
            case FAILED_FINAL -> "×";
            case SKIPPED_ALREADY_PROCESSED -> "≡";
            case SKIPPED_FINAL_FAILURE -> "⊘";
            case READY_FOR_AI -> "⟳";
            case PROPOSAL_READY -> "◇";
            case PROCESSING -> "▶";
        };
    }

    private static String statusColor(ProcessingStatus status) {
        if (status == null) return "#000000";
        return switch (status) {
            case SUCCESS -> "#2e7d32";
            case FAILED_RETRYABLE -> "#d98200";
            case FAILED_FINAL -> "#c62828";
            case SKIPPED_ALREADY_PROCESSED -> "#757575";
            case SKIPPED_FINAL_FAILURE -> "#424242";
            case READY_FOR_AI -> "#1565c0";
            case PROPOSAL_READY -> "#0288d1";
            case PROCESSING -> "#9e9e9e";
        };
    }

    private static String statusTooltip(ProcessingStatus status) {
        if (status == null) return "";
        return switch (status) {
            case SUCCESS -> "Erfolgreich verarbeitet und umbenannt.";
            case FAILED_RETRYABLE -> "Temporärer Fehler – wird beim nächsten Lauf automatisch erneut versucht.";
            case FAILED_FINAL -> "Dauerhaft nicht verarbeitbar – z. B. kein Textinhalt (Foto-PDF), "
                    + "Passwortschutz oder beschädigte Datei. Kein weiterer automatischer Versuch.";
            case SKIPPED_ALREADY_PROCESSED -> "Übersprungen – wurde bereits in einem früheren Lauf erfolgreich verarbeitet.";
            case SKIPPED_FINAL_FAILURE -> "Endgültig übersprungen nach wiederholten Fehlern.";
            case READY_FOR_AI -> "Wartet auf Verarbeitung.";
            case PROPOSAL_READY -> "KI-Vorschlag liegt vor, wartet auf Bestätigung.";
            case PROCESSING -> "Wird gerade verarbeitet.";
        };
    }

    private static <T> TableCell<T, String> ellipsisCell() {
        return new TableCell<>() {
            @Override
            protected void updateItem(String item, boolean empty) {
                super.updateItem(item, empty);
                if (empty || item == null) {
                    setText(null);
                    setTooltip(null);
                } else {
                    setText(item);
                    setTooltip(new Tooltip(item));
                }
            }
        };
    }

    private static Region spacerNew() {
        Region r = new Region();
        HBox.setHgrow(r, Priority.ALWAYS);
        return r;
    }

    private void showInfo(String message) {
        Alert alert = new Alert(Alert.AlertType.INFORMATION);
        alert.setTitle("Hinweis");
        alert.setHeaderText(null);
        alert.setContentText(message);
        alert.showAndWait();
    }
}
