Source: src/js/collections/versionHistory/VersionTimelineGroups.js

define(["backbone", "common/DateUtility"], (Backbone, DateUtility) => {
  "use strict";

  const NO_DATE_LABEL = "Unknown Date";

  /**
   * Normalize a model date into a grouping key and display values.
   * @param {Backbone.Model} model Version model.
   * @param {("local"|"UTC")} groupingTimeZone Day-boundary timezone.
   * @returns {{key: string, date: Date|null, label: string|null}} Normalized
   * date info for grouping and display.
   */
  const toSegmentDateInfo = (model, groupingTimeZone = "local") => {
    const rawDate = model.get("dateUploaded");
    const parsedDate = DateUtility.toDate(rawDate);
    if (!parsedDate) {
      return {
        key: "unknown",
        date: null,
        label: NO_DATE_LABEL,
      };
    }

    const normalizedDate = DateUtility.toMidnightDate(
      parsedDate,
      groupingTimeZone,
    );
    return {
      key: DateUtility.toDayId(normalizedDate, groupingTimeZone, "date"),
      date: normalizedDate,
      label: null,
    };
  };

  /**
   * Backbone collection representing grouped timeline entries for the version
   * history view. Each group is a contiguous segment of versions in chain order
   * that share the same calendar day (or "Unknown Date" label).
   * @since 2.37.0
   * @class VersionTimelineGroups
   * @classdesc A collection of Backbone models, each representing a contiguous
   * segment of versions for a specific date label/date.
   * @classcategory Collections/VersionHistory
   */
  const VersionTimelineGroups = Backbone.Collection.extend(
    /** @lends VersionTimelineGroups.prototype */ {
      type: "VersionTimelineGroups",

      /**
       * The model representing a group of versions for a specific date segment.
       */
      model: Backbone.Model.extend(
        /** @lends VersionTimelineGroupModel.prototype */ {
          type: "VersionTimelineGroupModel",
          idAttribute: "id",
          defaults() {
            return {
              id: "",
              sequence: 0,
              date: null,
              label: null,
              models: [],
            };
          },
        },
      ),

      /**
       * Sort groups strictly by their segment sequence in chain order.
       * @param {Backbone.Model} modelA First model.
       * @param {Backbone.Model} modelB Other model.
       * @returns {number} Comparator result.
       */
      comparator(modelA, modelB) {
        const seqA = modelA.get("sequence");
        const seqB = modelB.get("sequence");
        const finiteA = Number.isFinite(seqA);
        const finiteB = Number.isFinite(seqB);

        if (finiteA && finiteB) {
          if (seqA === seqB) return 0;
          return seqA < seqB ? -1 : 1;
        }
        if (finiteA !== finiteB) {
          return finiteA ? -1 : 1;
        }

        const idA = modelA.get("id") || "";
        const idB = modelB.get("id") || "";
        if (idA === idB) return 0;
        return idA < idB ? -1 : 1;
      },

      /**
       * Populate the collection from a DataONEObjects collection using strict
       * obsolescence-chain order, segmented into contiguous date buckets.
       * @param {DataONEObjects} collection DataONEObjects collection to group.
       * @param {object} [options] Options passed to `set`.
       * @param {boolean} [options.remove] Whether to remove existing models.
       * @param {string} options.referencePid Reference PID used to order the
       * chain via versionHistory indexes.
       * @param {("local"|"UTC")} [options.groupingTimeZone] Timezone
       * used to determine day boundaries.
       */
      fromDataONEObjects(collection, options = {}) {
        const {
          remove = true,
          referencePid = null,
          groupingTimeZone = "local",
          ...setOptions
        } = options;

        if (!referencePid) {
          throw new Error(
            "VersionTimelineGroups.fromDataONEObjects requires referencePid",
          );
        }

        const orderedModels = collection.getChainOrdered(referencePid);
        const groupedModels = [];

        let currentGroup = null;
        let currentKey = null;
        let sequence = 0;

        orderedModels.forEach((model) => {
          const dateInfo = toSegmentDateInfo(model, groupingTimeZone);
          const identifier = model.get("identifier") || `unknown-${sequence}`;
          const isNewGroup =
            currentGroup == null || dateInfo.key !== currentKey;

          if (isNewGroup) {
            currentKey = dateInfo.key;
            currentGroup = {
              // Keep group ids stable across incremental loads so Backbone can
              // merge/reorder existing groups instead of tearing them down.
              id: `segment:${dateInfo.key}:${identifier}`,
              sequence,
              date: dateInfo.date,
              label: dateInfo.label,
              models: [],
            };
            groupedModels.push(currentGroup);
            sequence += 1;
          }

          currentGroup.models.push(model);
        });

        this.set(groupedModels, { ...setOptions, remove });
      },
    },
  );

  VersionTimelineGroups.NO_DATE_LABEL = NO_DATE_LABEL;

  return VersionTimelineGroups;
});