import {
  REDIS_URL
} from "../../../chunk-H5NH7KPE.js";
import "../../../chunk-GYGNEQSC.js";
import {
  __decorateClass,
  __decorateParam
} from "../../../chunk-2E224ZSN.js";

// runtime/subsystems/events/event-bus.redis-backend.ts
import { Injectable, Inject, Logger } from "@nestjs/common";
var CHANNEL_PREFIX = "events:";
async function createRedisClient(url) {
  let Redis;
  try {
    const mod = await import("ioredis");
    Redis = mod.default ?? mod;
  } catch {
    throw new Error(
      'RedisEventBus requires the "ioredis" package. Install it with: npm install ioredis'
    );
  }
  return new Redis(url);
}
var RedisEventBus = class {
  constructor(redisUrl) {
    this.redisUrl = redisUrl;
  }
  redisUrl;
  logger = new Logger(RedisEventBus.name);
  publisher = null;
  subscriber = null;
  connected = false;
  /**
   * In-process subscriber registry. Handlers registered here are called when
   * a message arrives on the subscriber client — keeping fan-out within the
   * same process without an extra round-trip through Redis.
   */
  handlers = /* @__PURE__ */ new Map();
  /**
   * Track which event types have active Redis subscriptions.
   * Used to avoid subscribing multiple times to the same type channel.
   */
  subscribedTypes = /* @__PURE__ */ new Set();
  // ============================================================================
  // Lifecycle
  // ============================================================================
  async onModuleInit() {
    this.publisher = await createRedisClient(this.redisUrl);
    this.subscriber = await createRedisClient(this.redisUrl);
    this.publisher.on(
      "error",
      (err) => this.logger.error(`Redis publisher error: ${err.message}`, err.stack)
    );
    this.subscriber.on(
      "error",
      (err) => this.logger.error(`Redis subscriber error: ${err.message}`, err.stack)
    );
    this.subscriber.on("message", (channel, message) => {
      void this.handleMessage(channel, message);
    });
    this.connected = true;
    this.logger.log(`RedisEventBus connected to ${this.redisUrl}`);
  }
  async onModuleDestroy() {
    this.connected = false;
    if (this.subscriber) {
      await this.subscriber.unsubscribe();
      this.subscriber.disconnect();
      this.subscriber = null;
    }
    if (this.publisher) {
      this.publisher.disconnect();
      this.publisher = null;
    }
    this.subscribedTypes.clear();
    this.logger.log("RedisEventBus disconnected");
  }
  // ============================================================================
  // IEventBus
  // ============================================================================
  /**
   * Publish a single event.
   *
   * `tx` is accepted but ignored — see module-level JSDoc for details.
   */
  async publish(event, tx) {
    void tx;
    this.assertConnected();
    const payload = this.serialize(event);
    const channel = `${CHANNEL_PREFIX}${event.type}`;
    await this.publisher.publish(channel, payload);
  }
  /**
   * Publish multiple events using a pipeline so all PUBLISH commands are sent
   * in a single round-trip.
   *
   * `tx` is accepted but ignored — see module-level JSDoc for details.
   */
  async publishMany(events, tx) {
    void tx;
    if (events.length === 0) return;
    this.assertConnected();
    const pipeline = this.publisher.pipeline();
    for (const event of events) {
      const payload = this.serialize(event);
      const channel = `${CHANNEL_PREFIX}${event.type}`;
      pipeline.publish(channel, payload);
    }
    await pipeline.exec();
  }
  /**
   * Register a handler for a specific event type.
   * Returns an unsubscribe function — call it to remove the handler.
   *
   * On first handler for a type, subscribes to the per-type Redis channel.
   * On removal of the last handler for a type, unsubscribes from the channel.
   */
  /**
   * Lookup by id is unsupported on the Redis Pub/Sub backend — Pub/Sub
   * does not retain history. Always returns `null`. Logs a warning the
   * first time it's called so a misconfiguration surfaces visibly. Using
   * the bridge with the Redis backend is unsupported (the bridge requires
   * a durable event store).
   */
  warnedFindById = false;
  async findById(_eventId) {
    if (!this.warnedFindById) {
      this.warnedFindById = true;
      this.logger.warn(
        "RedisEventBus.findById is unsupported (Pub/Sub has no history). The bridge subsystem requires a durable event store; switch to DrizzleEventBus if you need bridge fanout."
      );
    }
    return null;
  }
  subscribe(eventType, handler) {
    if (!this.handlers.has(eventType)) {
      this.handlers.set(eventType, /* @__PURE__ */ new Set());
      void this.subscribeToType(eventType);
    }
    const set = this.handlers.get(eventType);
    const h = handler;
    set.add(h);
    return () => {
      set.delete(h);
      if (set.size === 0) {
        this.handlers.delete(eventType);
        void this.unsubscribeFromType(eventType);
      }
    };
  }
  // ============================================================================
  // Internal helpers
  // ============================================================================
  assertConnected() {
    if (!this.connected || !this.publisher) {
      throw new Error(
        "RedisEventBus is not connected. Ensure the module has been initialised before publishing."
      );
    }
  }
  serialize(event) {
    return JSON.stringify({
      ...event,
      occurredAt: event.occurredAt.toISOString()
    });
  }
  deserialize(raw) {
    const parsed = JSON.parse(raw);
    return {
      ...parsed,
      occurredAt: new Date(parsed.occurredAt)
    };
  }
  async handleMessage(channel, message) {
    let event;
    try {
      event = this.deserialize(message);
    } catch (err) {
      this.logger.warn(`Failed to deserialize event on channel "${channel}": ${err}`);
      return;
    }
    await this.dispatch(event);
  }
  async dispatch(event) {
    const set = this.handlers.get(event.type);
    if (!set) return;
    for (const handler of set) {
      try {
        await handler(event);
      } catch (err) {
        this.logger.error(
          `Handler error for event type "${event.type}" (id: ${event.id}): ${err}`
        );
      }
    }
  }
  /**
   * Subscribe to a per-type Redis channel.
   * Called lazily when the first handler is registered for a type.
   */
  async subscribeToType(eventType) {
    if (this.subscribedTypes.has(eventType)) {
      return;
    }
    const channel = `${CHANNEL_PREFIX}${eventType}`;
    try {
      await this.subscriber.subscribe(channel);
      this.subscribedTypes.add(eventType);
    } catch (err) {
      this.logger.error(`Failed to subscribe to channel "${channel}": ${err}`);
    }
  }
  /**
   * Unsubscribe from a per-type Redis channel.
   * Called when the last handler for a type is removed.
   */
  async unsubscribeFromType(eventType) {
    if (!this.subscribedTypes.has(eventType)) {
      return;
    }
    const channel = `${CHANNEL_PREFIX}${eventType}`;
    try {
      await this.subscriber.unsubscribe(channel);
      this.subscribedTypes.delete(eventType);
    } catch (err) {
      this.logger.error(`Failed to unsubscribe from channel "${channel}": ${err}`);
    }
  }
};
RedisEventBus = __decorateClass([
  Injectable(),
  __decorateParam(0, Inject(REDIS_URL))
], RedisEventBus);
export {
  RedisEventBus
};
//# sourceMappingURL=event-bus.redis-backend.js.map