import { BehaviorSubject, Observable, Subject } from 'rxjs';
import { distinctUntilChanged, filter, map, pluck } from 'rxjs/operators';

export class StoreInstanceService<T, R = void> {
  private events$ = new Subject<{ type: keyof R; data: R[keyof R] }>();
  private store$: BehaviorSubject<T>;

  constructor(initialState: T) {
    this.store$ = new BehaviorSubject<T>(initialState);
  }

  public fire<A extends keyof R>(type: A, data: R[A]): void {
    this.events$.next({ type, data });
  }

  public on<A extends keyof R>(type: A): Observable<R[keyof R]> {
    return this.events$.asObservable().pipe(
      filter((event) => event.type === type),
      map((event) => event.data)
    );
  }

  public select<A extends keyof T>(key1: A): Observable<T[A]>;
  public select<A extends keyof T, B extends keyof T[A]>(
    key1: A,
    key2: B
  ): Observable<T[A][B]>;
  public select(...paths: string[]): Observable<any> {
    return this.store$
      .asObservable()
      .pipe(pluck(...paths), distinctUntilChanged());
  }

  public update(reducer: (state: T) => T) {
    const state = this.store$.getValue();
    const newState = reducer(state);
    this.store$.next(newState);
  }
}
