/**
 * @wrnexus/pubsub — topic-based publish/subscribe with a pluggable driver.
 * The default is in-process; swap in a Redis/NATS driver for cross-instance
 * messaging (it also backs @wrnexus/core's realtime bridge).
 *
 *   const bus = createPubSub();
 *   const off = bus.subscribe("order:*", (msg, topic) => {...});
 *   await bus.publish("order:created", { id: 7 });
 *
 * Subscriptions match exact topics, "ns:*" prefixes, and "*" (everything).
 */

export type Handler<T = unknown> = (message: T, topic: string) => void | Promise<void>;

export interface PubSubDriver {
  publish(topic: string, message: unknown): void | Promise<void>;
  subscribe(pattern: string, handler: Handler): () => void;
}

export interface PubSub {
  publish<T = unknown>(topic: string, message: T): Promise<void>;
  subscribe<T = unknown>(pattern: string, handler: Handler<T>): () => void;
}

function patternMatches(pattern: string, topic: string): boolean {
  if (pattern === "*" || pattern === topic) return true;
  if (pattern.endsWith(":*")) return topic.startsWith(pattern.slice(0, -1)); // "post:" prefix
  return false;
}

/** In-process pub/sub driver (default). */
export function memoryDriver(): PubSubDriver {
  const subs = new Map<string, Set<Handler>>();
  return {
    publish(topic, message) {
      for (const [pattern, handlers] of subs) {
        if (!patternMatches(pattern, topic)) continue;
        for (const handler of handlers) void handler(message, topic);
      }
    },
    subscribe(pattern, handler) {
      let set = subs.get(pattern);
      if (!set) subs.set(pattern, (set = new Set()));
      set.add(handler);
      return () => {
        set!.delete(handler);
        if (!set!.size) subs.delete(pattern);
      };
    },
  };
}

/** Create a pub/sub bus over a driver (in-memory by default). */
export function createPubSub(driver: PubSubDriver = memoryDriver()): PubSub {
  return {
    async publish(topic, message) {
      await driver.publish(topic, message);
    },
    subscribe(pattern, handler) {
      return driver.subscribe(pattern, handler as Handler);
    },
  };
}
