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

import java.util.function.Function;

/**
 * Mediates the three-way protection dialog before any action that would discard unsaved changes.
 * <p>
 * The guard asks the user whether to save, discard or cancel the requested action.
 * The dialog interaction is injected via a {@link Function} so the guard can be tested
 * without a running JavaFX scene by substituting the real dialog with a stub.
 *
 * <p>Usage:
 * <ol>
 *   <li>Obtain an instance from the workspace.</li>
 *   <li>Call {@link #askAndProceed(String, Runnable, Runnable)} with the intended follow-up action.</li>
 *   <li>The guard shows the dialog when the editor is dirty and runs the follow-up only when
 *       it is safe to proceed.</li>
 * </ol>
 */
public final class GuiUnsavedChangesGuard {

    /**
     * The possible responses the user can give to the protection dialog.
     */
    public enum Choice {
        /** Save the current changes and then continue with the requested action. */
        SAVE,
        /** Discard all unsaved changes and continue with the requested action. */
        DISCARD,
        /** Cancel the requested action; no state change is performed. */
        CANCEL
    }

    /**
     * Supplies the user's choice for a given trigger label.
     * <p>
     * In production the function shows a modal dialog; in tests it can be replaced with a stub.
     */
    private Function<String, Choice> dialogSupplier;

    /**
     * Creates a guard that delegates the dialog interaction to the supplied function.
     *
     * @param dialogSupplier function that maps a trigger label to the user's choice; must not be {@code null}
     */
    public GuiUnsavedChangesGuard(Function<String, Choice> dialogSupplier) {
        this.dialogSupplier = dialogSupplier;
    }

    /**
     * Replaces the dialog supplier at runtime.
     * <p>
     * Package-private so tests can inject stubs without exposing setter to external callers.
     *
     * @param dialogSupplier the replacement function; must not be {@code null}
     */
    void setDialogSupplier(Function<String, Choice> dialogSupplier) {
        this.dialogSupplier = dialogSupplier;
    }

    /**
     * Asks the user how to handle unsaved changes before the named action and invokes the
     * appropriate callback.
     *
     * <ul>
     *   <li>{@link Choice#SAVE} → {@code onSave} is called; the caller must invoke
     *       {@code onProceed} itself after a successful save.</li>
     *   <li>{@link Choice#DISCARD} → {@code onProceed} is called immediately.</li>
     *   <li>{@link Choice#CANCEL} → neither callback is called.</li>
     * </ul>
     *
     * @param triggerLabel a short label identifying the triggering action (e.g. "Neu", "Öffnen");
     *                     used to give the dialog context; must not be {@code null}
     * @param onProceed    action to run when the user chose discard; must not be {@code null}
     * @param onSave       action to run when the user chose save; must not be {@code null}
     */
    public void askAndProceed(String triggerLabel, Runnable onProceed, Runnable onSave) {
        Choice choice = dialogSupplier.apply(triggerLabel);
        switch (choice) {
            case SAVE -> onSave.run();
            case DISCARD -> onProceed.run();
            case CANCEL -> {
                // No action – caller keeps the current state.
            }
        }
    }
}
