FeedImportSummary.java

package net.sasasin.sreader.service.feed.ingestion;

import net.sasasin.sreader.service.http.RedirectResolution;
import net.sasasin.sreader.service.outcome.OutcomePreconditions;

/**
 * Per-feed or accumulated counters for entry import outcomes. Counts are non-negative; use {@link
 * #plus(FeedImportSummary)} to accumulate without positional noise.
 */
public record FeedImportSummary(
    int entriesSeen,
    int insertedHeaders,
    int existingHeaders,
    int missingLinks,
    int redirectFallbacks,
    int feedTextsInserted,
    int feedTextsAlreadyPresent,
    int feedTextsWithoutContent) {

  public FeedImportSummary {
    entriesSeen = OutcomePreconditions.requireNonNegative("entriesSeen", entriesSeen);
    insertedHeaders = OutcomePreconditions.requireNonNegative("insertedHeaders", insertedHeaders);
    existingHeaders = OutcomePreconditions.requireNonNegative("existingHeaders", existingHeaders);
    missingLinks = OutcomePreconditions.requireNonNegative("missingLinks", missingLinks);
    redirectFallbacks =
        OutcomePreconditions.requireNonNegative("redirectFallbacks", redirectFallbacks);
    feedTextsInserted =
        OutcomePreconditions.requireNonNegative("feedTextsInserted", feedTextsInserted);
    feedTextsAlreadyPresent =
        OutcomePreconditions.requireNonNegative("feedTextsAlreadyPresent", feedTextsAlreadyPresent);
    feedTextsWithoutContent =
        OutcomePreconditions.requireNonNegative("feedTextsWithoutContent", feedTextsWithoutContent);
  }

  public static FeedImportSummary empty() {
    return new FeedImportSummary(0, 0, 0, 0, 0, 0, 0, 0);
  }

  public FeedImportSummary plus(FeedImportSummary other) {
    return new FeedImportSummary(
        entriesSeen + other.entriesSeen,
        insertedHeaders + other.insertedHeaders,
        existingHeaders + other.existingHeaders,
        missingLinks + other.missingLinks,
        redirectFallbacks + other.redirectFallbacks,
        feedTextsInserted + other.feedTextsInserted,
        feedTextsAlreadyPresent + other.feedTextsAlreadyPresent,
        feedTextsWithoutContent + other.feedTextsWithoutContent);
  }

  FeedImportSummary plusEntry(FeedEntryImportOutcome outcome) {
    FeedImportSummary base =
        new FeedImportSummary(
            entriesSeen + 1,
            insertedHeaders,
            existingHeaders,
            missingLinks,
            redirectFallbacks,
            feedTextsInserted,
            feedTextsAlreadyPresent,
            feedTextsWithoutContent);
    return switch (outcome) {
      case FeedEntryImportOutcome.Inserted inserted -> base.withInserted(inserted);
      case FeedEntryImportOutcome.AlreadyPresent already -> base.withExisting(already.redirect());
      case FeedEntryImportOutcome.MissingLink ignored ->
          new FeedImportSummary(
              base.entriesSeen,
              base.insertedHeaders,
              base.existingHeaders,
              base.missingLinks + 1,
              base.redirectFallbacks,
              base.feedTextsInserted,
              base.feedTextsAlreadyPresent,
              base.feedTextsWithoutContent);
      case FeedEntryImportOutcome.Failed ignored -> base;
    };
  }

  private FeedImportSummary withInserted(FeedEntryImportOutcome.Inserted inserted) {
    int redirects = redirectFallbacks + (isFallback(inserted.redirect()) ? 1 : 0);
    int textsInserted = feedTextsInserted;
    int textsExisting = feedTextsAlreadyPresent;
    int textsNone = feedTextsWithoutContent;
    if (inserted.feedTextWrite().isPresent()) {
      switch (inserted.feedTextWrite().get()) {
        case INSERTED -> textsInserted++;
        case ALREADY_EXISTS -> textsExisting++;
        case NO_CONTENT -> textsNone++;
      }
    }
    return new FeedImportSummary(
        entriesSeen,
        insertedHeaders + 1,
        existingHeaders,
        missingLinks,
        redirects,
        textsInserted,
        textsExisting,
        textsNone);
  }

  private FeedImportSummary withExisting(RedirectResolution redirect) {
    return new FeedImportSummary(
        entriesSeen,
        insertedHeaders,
        existingHeaders + 1,
        missingLinks,
        redirectFallbacks + (isFallback(redirect) ? 1 : 0),
        feedTextsInserted,
        feedTextsAlreadyPresent,
        feedTextsWithoutContent);
  }

  private static boolean isFallback(RedirectResolution redirect) {
    return redirect instanceof RedirectResolution.Fallback;
  }
}