/**
 * How to use a Signal:
 *
 * Create a signal:
 *    const chatSignal = new Signal<{ username: string, message: string }>()
 *
 * Connect to the signal:
 *    const disconnect = chatSignal.connect((data) => {
 *      const {username, message} = data;
 *      console.log(`${username} said "${message}"`);
 *    })
 *
 * Emit a signal:
 *    chatSignal.emit({ username: "Chad", message: "Hey everyone, how's it going?" });
 *
 *
 * Disconnect a single listener:
 *    disconnect(); // The disconnect function is returned when you connect to a signal
 *
 * Disconnect all listeners:
 *    chatSignal.disconnect()
 */

export class Signal<T extends object | void> {
  private listeners: Array<(data: T) => void> = []

  connect(listenerOrSignal: Signal<T> | ((data: T) => void)) {
    let listener: (data: T) => void

    // If it is a signal, forward the data to the signal
    if (listenerOrSignal instanceof Signal) {
      listener = (data: T) => listenerOrSignal.emit(data)
    } else {
      listener = listenerOrSignal
    }

    this.listeners.push(listener)

    return () => {
      this.listeners = this.listeners.filter((l) => l !== listener)
    }
  }

  emit(data: T) {
    for (const listener of this.listeners) {
      listener(data)
    }
  }

  disconnect() {
    this.listeners = []
  }
}

/**
 * How to use Emitter:
 *
 * Create an emitter:
 *    const chat = new Emitter<{ username: string, message: string }>()
 *
 * Listen to events:
 *    const off = chat.on((data) => {
 *      const {username, message} = data;
 *      console.log(`${username} said "${message}"`);
 *    })
 *
 * Emit an event:
 *    chat.emit({ username: "Chad", message: "Hey everyone, how's it going?" });
 *
 * Remove a specific listener:
 *    off(); // The off function is returned when you add a listener
 *
 * Remove all listeners:
 *    chat.removeAllListeners()
 */
export class Emitter<T = void> {
  private listeners: Array<(data: T) => void> = []

  on(listener: (data: T) => void) {
    this.listeners.push(listener)

    return () => {
      this.listeners = this.listeners.filter((l) => l !== listener)
    }
  }

  emit(data: T) {
    for (const listener of this.listeners) {
      listener(data)
    }
  }

  removeAllListeners() {
    this.listeners = []
  }
}
