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

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

/**
 * Outbound port for target folder access: duplicate resolution and best-effort cleanup.
 * <p>
 * The target folder is the directory where the renamed PDF copy is written. This port
 * encapsulates all target-folder concerns so that the application layer never handles
 * filesystem types ({@code Path}, {@code File}) directly.
 *
 * <h2>Duplicate resolution</h2>
 * <p>
 * When the base filename is already taken in the target folder, the port determines
 * the first available name by appending a numeric suffix directly before {@code .pdf}:
 * <pre>
 *   2024-01-15 - Rechnung.pdf
 *   2024-01-15 - Rechnung(1).pdf
 *   2024-01-15 - Rechnung(2).pdf
 *   ...
 * </pre>
 * The base filename must already include the {@code .pdf} extension. The suffix is
 * purely a technical collision-avoidance mechanism and introduces no new fachliche
 * title interpretation.
 *
 * <h2>Identical-content shortcut</h2>
 * <p>
 * Before appending any numeric suffix, the implementation checks whether the base name
 * already exists in the target folder <em>and</em> whether that existing file is
 * byte-for-byte identical to the source document (verified via the supplied
 * {@link DocumentFingerprint}). When both conditions hold, the method returns
 * {@link ExistingIdenticalTargetFile} instead of {@link ResolvedTargetFilename},
 * signalling that no new copy is required.
 *
 * <h2>Architecture boundary</h2>
 * <p>
 * No {@code Path}, {@code File}, or NIO types appear in this interface. The concrete
 * adapter implementation translates the opaque folder locator string to actual
 * filesystem operations.
 */
public interface TargetFolderPort {

    /**
     * Returns an opaque string that identifies the target folder managed by this port.
     * <p>
     * The application layer treats this as an opaque locator and stores it in the
     * document master record ({@code lastTargetPath}) for traceability. It must not
     * be interpreted by the application layer.
     *
     * @return a non-null, non-blank string identifying the target folder
     */
    String getTargetFolderLocator();

    /**
     * Resolves the first available unique filename in the target folder for the given base name,
     * taking the source document's fingerprint into account for identity-based shortcutting.
     * <p>
     * Processing order:
     * <ol>
     *   <li>If the base name does not yet exist in the target folder, return
     *       {@link ResolvedTargetFilename} with the base name.</li>
     *   <li>If the base name exists and its content is identical to the source document
     *       (SHA-256 comparison using {@code sourceFingerprint}), return
     *       {@link ExistingIdenticalTargetFile} — no new copy is needed.</li>
     *   <li>Otherwise append {@code (1)}, {@code (2)}, etc. directly before {@code .pdf}
     *       until a free name is found; return {@link ResolvedTargetFilename} with that name.</li>
     * </ol>
     * <p>
     * The returned filename contains only the file name, not the full path. It is safe
     * to use as the {@code resolvedFilename} parameter of
     * {@link TargetFileCopyPort#copyToTarget(de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator, String)}.
     *
     * @param baseName          the desired filename including the {@code .pdf} extension;
     *                          must not be null or blank
     * @param sourceFingerprint the SHA-256 fingerprint of the source document used for
     *                          identical-content detection; must not be null
     * @return a {@link ResolvedTargetFilename}, {@link ExistingIdenticalTargetFile}, or
     *         {@link TargetFolderTechnicalFailure}
     */
    TargetFilenameResolutionResult resolveUniqueFilename(String baseName, DocumentFingerprint sourceFingerprint);

    /**
     * Best-effort attempt to delete a file previously written to the target folder.
     * <p>
     * Intended for rollback after a successful target copy when subsequent persistence
     * fails. This method must not throw; if deletion fails for any reason, the failure
     * is silently ignored.
     *
     * @param resolvedFilename the filename (not full path) to delete; must not be null
     */
    void tryDeleteTargetFile(String resolvedFilename);
}
