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

import java.util.Optional;

/**
 * Outbound-Port für den exklusiven Run-Lock.
 * <p>
 * Stellt sicher, dass zu einem Zeitpunkt nur eine Instanz des PDF-Umbenenners
 * einen Verarbeitungslauf ausführt. Der Port abstrahiert den Mechanismus
 * (z.B. dateibasierter Lock) ohne eine konkrete Implementierung vorzuschreiben.
 * <p>
 * Verantwortlichkeiten:
 * <ul>
 *   <li>Exklusiven Zugriff auf gemeinsame Ressourcen (SQLite, Zielordner) sicherstellen</li>
 *   <li>Parallele Läufe verhindern</li>
 *   <li>Kontrollierten Startabbruch ermöglichen, wenn bereits eine Instanz läuft</li>
 * </ul>
 * <p>
 * Lock-Lifecycle (blockierender Pfad – headless, manueller GUI-Lauf und Scheduler-Tick):
 * <ol>
 *   <li>Lock beim Laufstart erwerben ({@link #acquire()})</li>
 *   <li>Lock für die gesamte Dauer des Laufs halten</li>
 *   <li>Lock am Laufende freigeben ({@link #release()}), auch bei Fehler</li>
 * </ol>
 * Der Scheduler-Tick verwendet dieselbe blockierende Methode {@link #acquire()} wie
 * der manuelle Laufpfad. {@link BatchRunProcessingUseCase#execute execute()} ruft
 * {@link #acquire()} intern auf; das Ergebnis {@code LOCK_UNAVAILABLE} signalisiert
 * dem Aufrufer, dass ein paralleler Lauf aktiv ist.
 * {@link #tryAcquire()} ist für Aufrufer vorgesehen, die außerhalb des Use-Case
 * einen schnellen, nicht-blockierenden Lock-Versuch benötigen.
 */
public interface RunLockPort {

    /**
     * Erwirbt den exklusiven Run-Lock (blockierend).
     * <p>
     * Wenn der Lock nicht erworben werden kann (z.B. weil eine andere
     * Instanz ihn bereits hält), wird eine {@link RunLockUnavailableException}
     * geworfen. Bei normalem Rücksprung hält der Aufrufer den Lock und muss
     * {@link #release()} in einem {@code finally}-Block aufrufen.
     *
     * @throws RunLockUnavailableException wenn der Lock nicht erworben werden kann
     * @throws RuntimeException bei anderen kritischen Fehlern
     */
    void acquire();

    /**
     * Gibt den exklusiven Run-Lock frei.
     * <p>
     * Wird nach Abschluss des Laufs (erfolgreich oder fehlerhaft) aufgerufen.
     * Implementierungen sollen keinen Fehler werfen, wenn der Lock bereits
     * freigegeben wurde oder gar nicht gehalten wird.
     *
     * @throws RuntimeException wenn die Freigabe kritisch fehlschlägt
     */
    void release();

    /**
     * Versucht nicht-blockierend, den Run-Lock zu erwerben.
     * <p>
     * Gibt ein {@link RunLockHandle} zurück, wenn der Lock erfolgreich
     * erworben wurde. Das Handle kann in einem try-with-resources-Block
     * verwendet werden; {@link RunLockHandle#close()} gibt den Lock
     * idempotent frei.
     * <p>
     * Ist der Lock bereits durch eine andere Instanz gehalten, wird sofort
     * {@link Optional#empty()} zurückgegeben – ohne zu warten oder zu queuen.
     * Diese Methode ist race-condition-sicher und frei von check-then-act-Mustern.
     *
     * @return Handle mit dem erworbenen Lock, oder {@link Optional#empty()}
     *         wenn der Lock nicht verfügbar ist
     * @throws RuntimeException bei kritischen technischen Fehlern beim
     *         Lock-Versuch (nicht bei normaler Nicht-Verfügbarkeit)
     */
    Optional<RunLockHandle> tryAcquire();
}
