package de.gecheckt.pdf.umbenenner.application.port.in;

import de.gecheckt.pdf.umbenenner.domain.model.RunId;

/**
 * Inbound observer port that receives progress callbacks over the life of a single
 * batch run.
 * <p>
 * The observer is an optional collaborator that an inbound adapter (e.g. a GUI) may
 * supply to follow a batch run in near real time. The callbacks never carry persistence
 * details; they only describe observable events at the use-case boundary.
 *
 * <h2>Invocation order</h2>
 * <p>
 * For a single run the observer is invoked in this order:
 * <ol>
 *   <li>{@link #onRunStarted(RunId, int)} exactly once, once the total candidate count
 *       is known (i.e. after the source folder scan succeeded and before the first
 *       candidate is processed).</li>
 *   <li>{@link #onDocumentCompleted(DocumentCompletionEvent)} once per candidate whose
 *       processing reached a terminal resolution.</li>
 *   <li>{@link #onRunEnded(RunSummary)} exactly once after the processing loop has
 *       finished (normally, after a cancellation, or after a hard run-level error).</li>
 * </ol>
 *
 * <h2>Threading</h2>
 * <p>
 * Callbacks are invoked on the thread executing the batch run. Inbound adapters that
 * drive a UI must themselves dispatch any UI updates onto the appropriate UI thread
 * and must not block the reporting thread.
 *
 * <h2>Exception handling</h2>
 * <p>
 * Implementations must not throw checked exceptions. Runtime exceptions thrown by an
 * observer are caught by the application layer and logged; they never affect the batch
 * run outcome or alter persistence behaviour.
 */
public interface BatchRunProgressObserver {

    /**
     * Invoked once when the run has determined how many candidates will be processed.
     *
     * @param runId           identifier of the run; never {@code null}
     * @param totalCandidates total number of candidates detected in the source folder
     *                        at scan time; never negative
     */
    void onRunStarted(RunId runId, int totalCandidates);

    /**
     * Invoked once per candidate whose processing reached a terminal resolution.
     * <p>
     * The event is emitted after persistence has been attempted for the candidate, so
     * observers may rely on the reported status matching the persisted attempt status
     * for that candidate.
     *
     * @param event description of the candidate result; never {@code null}
     */
    void onDocumentCompleted(DocumentCompletionEvent event);

    /**
     * Invoked once after the processing loop has finished, regardless of whether the
     * run completed normally, was cancelled via a {@link BatchRunCancellationToken},
     * or aborted due to a hard run-level error after the start callback fired.
     *
     * @param summary aggregated outcome counts; never {@code null}
     */
    void onRunEnded(RunSummary summary);

    /**
     * Returns a singleton observer that silently ignores all callbacks.
     * <p>
     * Used as the default observer for callers that do not need progress notifications
     * (e.g. the headless batch entry point).
     *
     * @return a non-null no-op observer
     */
    static BatchRunProgressObserver noOp() {
        return NoOpBatchRunProgressObserver.INSTANCE;
    }
}
