"use strict";
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const expressions = require("../compiler/expressions.cjs");
const utils = require("./utils.cjs");
const loadMoreCallbackSymbol = /* @__PURE__ */ Symbol.for(
  `@tanstack/db.collection-config-builder`
);
class CollectionSubscriber {
  constructor(alias, collectionId, collection, collectionConfigBuilder) {
    this.alias = alias;
    this.collectionId = collectionId;
    this.collection = collection;
    this.collectionConfigBuilder = collectionConfigBuilder;
    this.biggest = void 0;
    this.subscriptionLoadingPromises = /* @__PURE__ */ new Map();
    this.sentToD2Keys = /* @__PURE__ */ new Set();
  }
  subscribe() {
    const whereClause = this.getWhereClauseForAlias();
    if (whereClause) {
      const whereExpression = expressions.normalizeExpressionPaths(whereClause, this.alias);
      return this.subscribeToChanges(whereExpression);
    }
    return this.subscribeToChanges();
  }
  subscribeToChanges(whereExpression) {
    const orderByInfo = this.getOrderByInfo();
    const trackLoadResult = (result) => {
      if (result instanceof Promise) {
        this.collectionConfigBuilder.liveQueryCollection._sync.trackLoadPromise(
          result
        );
      }
    };
    const onStatusChange = (event) => {
      const subscription2 = event.subscription;
      if (event.status === `loadingSubset`) {
        this.ensureLoadingPromise(subscription2);
      } else {
        const deferred = this.subscriptionLoadingPromises.get(subscription2);
        if (deferred) {
          this.subscriptionLoadingPromises.delete(subscription2);
          deferred.resolve();
        }
      }
    };
    let subscription;
    if (orderByInfo) {
      subscription = this.subscribeToOrderedChanges(
        whereExpression,
        orderByInfo,
        onStatusChange,
        trackLoadResult
      );
    } else {
      const includeInitialState = !this.collectionConfigBuilder.isLazyAlias(
        this.alias
      );
      subscription = this.subscribeToMatchingChanges(
        whereExpression,
        includeInitialState,
        onStatusChange
      );
    }
    if (subscription.status === `loadingSubset`) {
      this.ensureLoadingPromise(subscription);
    }
    const unsubscribe = () => {
      const deferred = this.subscriptionLoadingPromises.get(subscription);
      if (deferred) {
        this.subscriptionLoadingPromises.delete(subscription);
        deferred.resolve();
      }
      subscription.unsubscribe();
    };
    this.collectionConfigBuilder.currentSyncState.unsubscribeCallbacks.add(
      unsubscribe
    );
    return subscription;
  }
  sendChangesToPipeline(changes, callback) {
    const changesArray = Array.isArray(changes) ? changes : [...changes];
    const filteredChanges = utils.filterDuplicateInserts(
      changesArray,
      this.sentToD2Keys
    );
    const input = this.collectionConfigBuilder.currentSyncState.inputs[this.alias];
    const sentChanges = utils.sendChangesToInput(
      input,
      filteredChanges,
      this.collection.config.getKey
    );
    const dataLoader = sentChanges > 0 ? callback : void 0;
    this.collectionConfigBuilder.scheduleGraphRun(dataLoader, {
      alias: this.alias
    });
  }
  subscribeToMatchingChanges(whereExpression, includeInitialState, onStatusChange) {
    const sendChanges = (changes) => {
      this.sendChangesToPipeline(changes);
    };
    const hints = utils.computeSubscriptionOrderByHints(
      this.collectionConfigBuilder.query,
      this.alias
    );
    const onLoadSubsetResult = includeInitialState ? (result) => {
      if (result instanceof Promise) {
        this.collectionConfigBuilder.liveQueryCollection._sync.trackLoadPromise(
          result
        );
      }
    } : void 0;
    const subscription = this.collection.subscribeChanges(sendChanges, {
      ...includeInitialState && { includeInitialState },
      whereExpression,
      onStatusChange,
      orderBy: hints.orderBy,
      limit: hints.limit,
      onLoadSubsetResult
    });
    return subscription;
  }
  subscribeToOrderedChanges(whereExpression, orderByInfo, onStatusChange, onLoadSubsetResult) {
    const { orderBy, offset, limit, index } = orderByInfo;
    const handleLoadSubsetResult = (result) => {
      if (result instanceof Promise) {
        this.pendingOrderedLoadPromise = result;
        result.finally(() => {
          if (this.pendingOrderedLoadPromise === result) {
            this.pendingOrderedLoadPromise = void 0;
          }
        });
      }
      onLoadSubsetResult(result);
    };
    this.orderedLoadSubsetResult = handleLoadSubsetResult;
    const subscriptionHolder = {};
    const sendChangesInRange = (changes) => {
      const changesArray = Array.isArray(changes) ? changes : [...changes];
      this.trackSentValues(changesArray, orderByInfo.comparator);
      const splittedChanges = utils.splitUpdates(changesArray);
      this.sendChangesToPipelineWithTracking(
        splittedChanges,
        subscriptionHolder.current
      );
    };
    const subscription = this.collection.subscribeChanges(sendChangesInRange, {
      whereExpression,
      onStatusChange
    });
    subscriptionHolder.current = subscription;
    const truncateUnsubscribe = this.collection.on(`truncate`, () => {
      this.biggest = void 0;
      this.lastLoadRequestKey = void 0;
      this.pendingOrderedLoadPromise = void 0;
      this.sentToD2Keys.clear();
    });
    subscription.on(`unsubscribed`, () => {
      truncateUnsubscribe();
    });
    const normalizedOrderBy = expressions.normalizeOrderByPaths(orderBy, this.alias);
    if (index) {
      subscription.setOrderByIndex(index);
      subscription.requestLimitedSnapshot({
        limit: offset + limit,
        orderBy: normalizedOrderBy,
        trackLoadSubsetPromise: false,
        onLoadSubsetResult: handleLoadSubsetResult
      });
    } else {
      subscription.requestSnapshot({
        orderBy: normalizedOrderBy,
        limit: offset + limit,
        trackLoadSubsetPromise: false,
        onLoadSubsetResult: handleLoadSubsetResult
      });
    }
    return subscription;
  }
  // This function is called by maybeRunGraph
  // after each iteration of the query pipeline
  // to ensure that the orderBy operator has enough data to work with
  loadMoreIfNeeded(subscription) {
    const orderByInfo = this.getOrderByInfo();
    if (!orderByInfo) {
      return true;
    }
    const { dataNeeded } = orderByInfo;
    if (!dataNeeded) {
      return true;
    }
    if (this.pendingOrderedLoadPromise) {
      return true;
    }
    const n = dataNeeded();
    if (n > 0) {
      this.loadNextItems(n, subscription);
    }
    return true;
  }
  sendChangesToPipelineWithTracking(changes, subscription) {
    const orderByInfo = this.getOrderByInfo();
    if (!orderByInfo) {
      this.sendChangesToPipeline(changes);
      return;
    }
    const subscriptionWithLoader = subscription;
    subscriptionWithLoader[loadMoreCallbackSymbol] ??= this.loadMoreIfNeeded.bind(this, subscription);
    this.sendChangesToPipeline(
      changes,
      subscriptionWithLoader[loadMoreCallbackSymbol]
    );
  }
  // Loads the next `n` items from the collection
  // starting from the biggest item it has sent
  loadNextItems(n, subscription) {
    const orderByInfo = this.getOrderByInfo();
    if (!orderByInfo) {
      return;
    }
    const cursor = utils.computeOrderedLoadCursor(
      orderByInfo,
      this.biggest,
      this.lastLoadRequestKey,
      this.alias,
      n
    );
    if (!cursor) return;
    this.lastLoadRequestKey = cursor.loadRequestKey;
    subscription.requestLimitedSnapshot({
      orderBy: cursor.normalizedOrderBy,
      limit: n,
      minValues: cursor.minValues,
      trackLoadSubsetPromise: false,
      onLoadSubsetResult: this.orderedLoadSubsetResult
    });
  }
  getWhereClauseForAlias() {
    const sourceWhereClausesCache = this.collectionConfigBuilder.sourceWhereClausesCache;
    if (!sourceWhereClausesCache) {
      return void 0;
    }
    return sourceWhereClausesCache.get(this.alias);
  }
  getOrderByInfo() {
    const info = this.collectionConfigBuilder.optimizableOrderByCollections[this.collectionId];
    if (info && info.alias === this.alias) {
      return info;
    }
    return void 0;
  }
  trackSentValues(changes, comparator) {
    const result = utils.trackBiggestSentValue(
      changes,
      this.biggest,
      this.sentToD2Keys,
      comparator
    );
    this.biggest = result.biggest;
    if (result.shouldResetLoadKey) {
      this.lastLoadRequestKey = void 0;
    }
  }
  ensureLoadingPromise(subscription) {
    if (this.subscriptionLoadingPromises.has(subscription)) {
      return;
    }
    let resolve;
    const promise = new Promise((res) => {
      resolve = res;
    });
    this.subscriptionLoadingPromises.set(subscription, {
      resolve
    });
    this.collectionConfigBuilder.liveQueryCollection._sync.trackLoadPromise(
      promise
    );
  }
}
exports.CollectionSubscriber = CollectionSubscriber;
//# sourceMappingURL=collection-subscriber.cjs.map
