import Redis from "ioredis";
import type { PubSubEvents } from "./events";

const redisPub = new Redis();
const redisSub = new Redis();

export const redisPublish = <T extends keyof PubSubEvents>(
  event: T,
  data: PubSubEvents[T],
) => {
  redisPub.publish(event, JSON.stringify(data));
};

const redisSubscribe = <T extends keyof PubSubEvents>(
  event: T,
  callback: (data: PubSubEvents[T]) => void,
) => {
  redisSub.subscribe(event, (err) => {
    if (err) console.error(`Redis subscription error for ${event}:`, err);
  });

  redisSub.on("message", (channel, message) => {
    if (channel === event) {
      callback(JSON.parse(message) as PubSubEvents[T]);
    }
  });
};

/**
 * Creates an async iterator for a Redis subscription channel.
 * @param event - The Redis event to subscribe to.
 * @returns An async iterator that yields tracked events.
 */
export function redisAsyncIterator<K extends keyof PubSubEvents>(
  event: K,
): AsyncIterableIterator<PubSubEvents[K]> {
  return {
    [Symbol.asyncIterator]() {
      return this;
    },
    async next(): Promise<IteratorResult<PubSubEvents[K]>> {
      return new Promise((resolve) => {
        redisSubscribe(event, (data: PubSubEvents[K]) => {
          resolve({ value: data, done: false });
        });
      });
    },
  };
}

export async function* combineRedisIterators<T extends keyof PubSubEvents>(
  events: T[],
) {
  const iterators = events.map((event) =>
    redisAsyncIterator(event)[Symbol.asyncIterator](),
  );

  while (true) {
    // Wait for the next event from any iterator
    const result = await Promise.race(
      iterators.map((iterator, index) =>
        iterator.next().then((untypedRes) => {
          const res = untypedRes as IteratorResult<PubSubEvents[T]>;
          return { res, event: events[index] };
        }),
      ),
    );

    if (result.res.done) break;

    yield { event: result.event, data: result.res.value };
  }
}
