/**
 * @description Queueable Apex class for {describe the async operation}.
 *              Accepts data through the constructor for stateful processing.
 *              Optionally implements Database.AllowsCallouts for external integrations.
 * @author {Author}
 *
 * @example
 * // Enqueue the job
 * Id jobId = System.enqueueJob(new {ClassName}(recordIds));
 */
public with sharing class {ClassName} implements Queueable /*, Database.AllowsCallouts */ {

    // ─── Constants ───────────────────────────────────────────────────────
    private static final Integer MAX_CHAIN_DEPTH = 5;

    // ─── Instance Variables (Stateful) ───────────────────────────────────
    private Set<Id> recordIds;
    private Integer chainDepth;

    // ─── Constructors ────────────────────────────────────────────────────

    /**
     * @description Creates a new queueable job to process the specified records
     * @param recordIds Set of record Ids to process
     */
    public {ClassName}(Set<Id> recordIds) {
        this(recordIds, 0);
    }

    /**
     * @description Creates a new queueable job with chain depth tracking
     * @param recordIds Set of record Ids to process
     * @param chainDepth Current depth in the queueable chain
     */
    public {ClassName}(Set<Id> recordIds, Integer chainDepth) {
        this.recordIds = recordIds ?? new Set<Id>();
        this.chainDepth = chainDepth;
    }

    // ─── Queueable Interface ─────────────────────────────────────────────

    /**
     * @description Executes the asynchronous work
     * @param context The queueable context
     */
    public void execute(QueueableContext context) {
        if (this.recordIds.isEmpty()) {
            return;
        }

        try {
            // TODO: Implement the async processing logic
            // List<{SObject}> records = {SObject}Selector.selectByIds(this.recordIds);
            // ... process records ...

            // Chain to next job if there's more work and we haven't hit the depth limit
            chainIfNeeded();

        } catch (Exception e) {
            handleError(context.getJobId(), e);
        }
    }

    // ─── Private Helpers ─────────────────────────────────────────────────

    /**
     * @description Chains to the next queueable job if needed, with depth guard
     */
    private void chainIfNeeded() {
        // TODO: Determine if chaining is needed (e.g., remaining records to process)
        Set<Id> remainingIds = new Set<Id>();

        if (!remainingIds.isEmpty() && this.chainDepth < MAX_CHAIN_DEPTH) {
            if (!Test.isRunningTest()) {
                System.enqueueJob(new {ClassName}(remainingIds, this.chainDepth + 1));
            }
        }
    }

    /**
     * @description Handles errors during execution
     * @param jobId The async job Id
     * @param e The exception that occurred
     */
    private void handleError(Id jobId, Exception e) {
        System.debug(LoggingLevel.ERROR,
            '{ClassName} failed (Job: ' + jobId + '): ' +
            e.getMessage() + '\n' + e.getStackTraceString()
        );
        // TODO: Persist error to a log object or send notification
    }
}
