import { NegativeActiveSubscribersError } from "../errors.js";
import { createSingleRowRefProxy, toExpression } from "../query/builder/ref-proxy.js";
import { CollectionSubscription } from "./subscription.js";
class CollectionChangesManager {
  /**
   * Creates a new CollectionChangesManager instance
   */
  constructor() {
    this.activeSubscribersCount = 0;
    this.changeSubscriptions = /* @__PURE__ */ new Set();
    this.batchedEvents = [];
    this.shouldBatchEvents = false;
  }
  setDeps(deps) {
    this.lifecycle = deps.lifecycle;
    this.sync = deps.sync;
    this.events = deps.events;
    this.collection = deps.collection;
    this.state = deps.state;
  }
  /**
   * Emit an empty ready event to notify subscribers that the collection is ready
   * This bypasses the normal empty array check in emitEvents
   */
  emitEmptyReadyEvent() {
    for (const subscription of this.changeSubscriptions) {
      subscription.emitEvents([]);
    }
  }
  /**
   * Enriches a change message with virtual properties ($synced, $origin, $key, $collectionId).
   * Uses the "add-if-missing" pattern to preserve virtual properties from upstream collections.
   */
  enrichChangeWithVirtualProps(change) {
    return this.state.enrichChangeMessage(change);
  }
  /**
   * Emit events either immediately or batch them for later emission
   */
  emitEvents(changes, forceEmit = false) {
    if (this.shouldBatchEvents && !forceEmit) {
      this.batchedEvents.push(...changes);
      return;
    }
    let rawEvents = changes;
    if (forceEmit) {
      if (this.batchedEvents.length > 0) {
        rawEvents = [...this.batchedEvents, ...changes];
      }
      this.batchedEvents = [];
      this.shouldBatchEvents = false;
    }
    if (rawEvents.length === 0) {
      return;
    }
    const enrichedEvents = rawEvents.map((change) => this.enrichChangeWithVirtualProps(change));
    for (const subscription of this.changeSubscriptions) {
      subscription.emitEvents(enrichedEvents);
    }
  }
  /**
   * Subscribe to changes in the collection
   */
  subscribeChanges(callback, options = {}) {
    this.addSubscriber();
    if (options.where && options.whereExpression) {
      throw new Error(
        `Cannot specify both 'where' and 'whereExpression' options. Use one or the other.`
      );
    }
    const { where, ...opts } = options;
    let whereExpression = opts.whereExpression;
    if (where) {
      const proxy = createSingleRowRefProxy();
      const result = where(proxy);
      whereExpression = toExpression(result);
    }
    const subscription = new CollectionSubscription(this.collection, callback, {
      ...opts,
      whereExpression,
      onUnsubscribe: () => {
        this.removeSubscriber();
        this.changeSubscriptions.delete(subscription);
      }
    });
    if (options.onStatusChange) {
      subscription.on(`status:change`, options.onStatusChange);
    }
    if (options.includeInitialState) {
      subscription.requestSnapshot({
        trackLoadSubsetPromise: false,
        orderBy: options.orderBy,
        limit: options.limit,
        onLoadSubsetResult: options.onLoadSubsetResult
      });
    } else if (options.includeInitialState === false) {
      subscription.markAllStateAsSeen();
    }
    this.changeSubscriptions.add(subscription);
    return subscription;
  }
  /**
   * Increment the active subscribers count and start sync if needed
   */
  addSubscriber() {
    const previousSubscriberCount = this.activeSubscribersCount;
    this.activeSubscribersCount++;
    this.lifecycle.cancelGCTimer();
    if (this.lifecycle.status === `cleaned-up` || this.lifecycle.status === `idle`) {
      this.sync.startSync();
    }
    this.events.emitSubscribersChange(
      this.activeSubscribersCount,
      previousSubscriberCount
    );
  }
  /**
   * Decrement the active subscribers count and start GC timer if needed
   */
  removeSubscriber() {
    const previousSubscriberCount = this.activeSubscribersCount;
    this.activeSubscribersCount--;
    if (this.activeSubscribersCount === 0) {
      this.lifecycle.startGCTimer();
    } else if (this.activeSubscribersCount < 0) {
      throw new NegativeActiveSubscribersError();
    }
    this.events.emitSubscribersChange(
      this.activeSubscribersCount,
      previousSubscriberCount
    );
  }
  /**
   * Clean up the collection by stopping sync and clearing data
   * This can be called manually or automatically by garbage collection
   */
  cleanup() {
    this.batchedEvents = [];
    this.shouldBatchEvents = false;
  }
}
export {
  CollectionChangesManager
};
//# sourceMappingURL=changes.js.map
