'use strict';

var EventEmitter = require('events');

function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }

var EventEmitter__default = /*#__PURE__*/_interopDefault(EventEmitter);

// src/events/pubsub.ts
var PubSub = class {
};
var EventEmitterPubSub = class extends PubSub {
  emitter;
  // group → topic → callbacks[]
  groups = /* @__PURE__ */ new Map();
  // "topic:group" → round-robin counter
  groupCounters = /* @__PURE__ */ new Map();
  // "topic:group" → the single listener registered on the emitter for this group
  groupListeners = /* @__PURE__ */ new Map();
  // Track pending nack redeliveries so flush() can wait and close() can cancel them
  pendingNacks = /* @__PURE__ */ new Set();
  // Track delivery attempts per message id
  deliveryAttempts = /* @__PURE__ */ new Map();
  // topic → (original callback → wrapped listener) for fan-out (non-group) subscribers.
  // Nested keying so the same callback registered on multiple topics keeps
  // a distinct wrapper per topic.
  fanoutWrappers = /* @__PURE__ */ new Map();
  constructor(existingEmitter) {
    super();
    this.emitter = existingEmitter ?? new EventEmitter__default.default();
  }
  async publish(topic, event) {
    const id = crypto.randomUUID();
    const createdAt = /* @__PURE__ */ new Date();
    this.emitter.emit(topic, {
      ...event,
      id,
      createdAt,
      deliveryAttempt: 1
    });
  }
  async subscribe(topic, cb, options) {
    if (options?.group) {
      this.subscribeWithGroup(topic, cb, options.group);
    } else {
      const wrapper = (event) => {
        cb(
          event,
          async () => {
          },
          async () => {
          }
        );
      };
      let byCb = this.fanoutWrappers.get(topic);
      if (!byCb) {
        byCb = /* @__PURE__ */ new Map();
        this.fanoutWrappers.set(topic, byCb);
      }
      byCb.set(cb, wrapper);
      this.emitter.on(topic, wrapper);
    }
  }
  async unsubscribe(topic, cb) {
    for (const [group, topicMap] of this.groups) {
      const members = topicMap.get(topic);
      if (members) {
        const idx = members.indexOf(cb);
        if (idx !== -1) {
          members.splice(idx, 1);
          if (members.length === 0) {
            topicMap.delete(topic);
            const listenerKey = `${topic}:${group}`;
            const listener = this.groupListeners.get(listenerKey);
            if (listener) {
              this.emitter.off(topic, listener);
              this.groupListeners.delete(listenerKey);
              this.groupCounters.delete(listenerKey);
            }
          }
          if (topicMap.size === 0) {
            this.groups.delete(group);
          }
          return;
        }
      }
    }
    const byCb = this.fanoutWrappers.get(topic);
    const wrapper = byCb?.get(cb);
    if (wrapper && byCb) {
      this.emitter.off(topic, wrapper);
      byCb.delete(cb);
      if (byCb.size === 0) this.fanoutWrappers.delete(topic);
    } else {
      this.emitter.off(topic, cb);
    }
  }
  async flush() {
    if (this.pendingNacks.size > 0) {
      await new Promise((resolve) => {
        const check = () => {
          if (this.pendingNacks.size === 0) {
            resolve();
          } else {
            setTimeout(check, 10);
          }
        };
        check();
      });
    }
  }
  /**
   * Clean up all listeners during graceful shutdown.
   */
  async close() {
    for (const handle of this.pendingNacks) {
      clearTimeout(handle);
    }
    this.pendingNacks.clear();
    this.deliveryAttempts.clear();
    this.emitter.removeAllListeners();
    this.groups.clear();
    this.groupCounters.clear();
    this.groupListeners.clear();
    this.fanoutWrappers.clear();
  }
  subscribeWithGroup(topic, cb, group) {
    let topicMap = this.groups.get(group);
    if (!topicMap) {
      topicMap = /* @__PURE__ */ new Map();
      this.groups.set(group, topicMap);
    }
    let members = topicMap.get(topic);
    if (!members) {
      members = [];
      topicMap.set(topic, members);
    }
    members.push(cb);
    const listenerKey = `${topic}:${group}`;
    if (!this.groupListeners.has(listenerKey)) {
      const listener = (event) => {
        this.deliverToGroup(topic, group, listenerKey, event);
      };
      this.groupListeners.set(listenerKey, listener);
      this.emitter.on(topic, listener);
    }
  }
  deliverToGroup(topic, group, listenerKey, event) {
    const currentMembers = this.groups.get(group)?.get(topic);
    if (!currentMembers || currentMembers.length === 0) return;
    const counter = this.groupCounters.get(listenerKey) ?? 0;
    const idx = counter % currentMembers.length;
    this.groupCounters.set(listenerKey, counter + 1);
    const attemptKey = `${listenerKey}:${event.id}`;
    const attempt = this.deliveryAttempts.get(attemptKey) ?? 1;
    const eventWithAttempt = { ...event, deliveryAttempt: attempt };
    const ack = async () => {
      this.deliveryAttempts.delete(attemptKey);
    };
    const nack = async () => {
      this.deliveryAttempts.set(attemptKey, attempt + 1);
      const handle = setTimeout(() => {
        this.pendingNacks.delete(handle);
        this.deliverToGroup(topic, group, listenerKey, event);
      }, 0);
      this.pendingNacks.add(handle);
    };
    currentMembers[idx](eventWithAttempt, ack, nack);
  }
};

exports.EventEmitterPubSub = EventEmitterPubSub;
exports.PubSub = PubSub;
//# sourceMappingURL=chunk-DGN2UMWP.cjs.map
//# sourceMappingURL=chunk-DGN2UMWP.cjs.map