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

/**
 * Immutable snapshot of the two independent failure counters maintained per document.
 * <p>
 * Two distinct counters are tracked separately because they drive different retry rules:
 * <ul>
 *   <li><strong>Content error counter</strong> ({@link #contentErrorCount()}):
 *       counts how many times a deterministic content error occurred for this document
 *       (no usable text, page limit exceeded, AI functional failure, ambiguous content).
 *       At count&nbsp;1 the document transitions to {@code FAILED_RETRYABLE};
 *       at count&nbsp;2 it transitions to {@code FAILED_FINAL}.
 *       Skip events do <em>not</em> increase this counter.</li>
 *   <li><strong>Transient error counter</strong> ({@link #transientErrorCount()}):
 *       counts how many times a transient technical error occurred after a successful
 *       fingerprint was computed. The document remains {@code FAILED_RETRYABLE} while
 *       this counter is strictly less than the configured {@code max.retries.transient}
 *       value. The attempt that causes the counter to reach {@code max.retries.transient}
 *       transitions the document to {@code FAILED_FINAL}.
 *       The configured limit must be an Integer &ge; 1.
 *       Skip events do <em>not</em> increase this counter.</li>
 * </ul>
 * <p>
 * <strong>Immediate within-run target copy retry:</strong>
 * The physical target-copy retry within the same run is not tracked in either counter.
 * It is a purely technical within-run mechanism and does not affect the
 * laufübergreifenden counter state.
 * <p>
 * <strong>Counter invariant:</strong>
 * Both counters start at zero for a newly discovered document and only increase
 * monotonically. The counters are written by the repository layer on the instructions
 * of the application use case; they never change as a side-effect of a read operation.
 *
 * @param contentErrorCount   number of historised deterministic content errors;
 *                            must be &ge; 0
 * @param transientErrorCount number of historised transient technical errors;
 *                            must be &ge; 0
 */
public record FailureCounters(int contentErrorCount, int transientErrorCount) {

    /**
     * Compact constructor validating that neither counter is negative.
     *
     * @throws IllegalArgumentException if either counter is negative
     */
    public FailureCounters {
        if (contentErrorCount < 0) {
            throw new IllegalArgumentException(
                    "contentErrorCount must be >= 0, but was: " + contentErrorCount);
        }
        if (transientErrorCount < 0) {
            throw new IllegalArgumentException(
                    "transientErrorCount must be >= 0, but was: " + transientErrorCount);
        }
    }

    /**
     * Returns a {@code FailureCounters} instance with both counters at zero.
     * Use this when initialising a master record for a newly discovered document.
     *
     * @return zero-value counters
     */
    public static FailureCounters zero() {
        return new FailureCounters(0, 0);
    }

    /**
     * Returns a copy with the content error counter incremented by one.
     *
     * @return new instance with {@code contentErrorCount + 1}
     */
    public FailureCounters withIncrementedContentErrorCount() {
        return new FailureCounters(contentErrorCount + 1, transientErrorCount);
    }

    /**
     * Returns a copy with the transient error counter incremented by one.
     *
     * @return new instance with {@code transientErrorCount + 1}
     */
    public FailureCounters withIncrementedTransientErrorCount() {
        return new FailureCounters(contentErrorCount, transientErrorCount + 1);
    }
}
