import { Injectable } from '@angular/core';
import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout';
import { map, Observable, ReplaySubject, switchMap } from 'rxjs';
import { fromPairs } from 'lodash-es';
import { FuseConfigService } from '@fuse/services/config';
import { Dictionary } from 'lodash';

@Injectable()
export class FuseMediaWatcherService {
  private _onMediaChange: ReplaySubject<{
    matchingAliases: string[];
    matchingQueries: any;
  }> = new ReplaySubject<{ matchingAliases: string[]; matchingQueries: any }>(
    1
  );

  /**
   * Constructor
   */
  constructor(
    private _breakpointObserver: BreakpointObserver,
    private _fuseConfigService: FuseConfigService
  ) {
    this._fuseConfigService.config$
      .pipe(
        map((config: any) =>
          fromPairs(
            Object.entries(config.screens).map(([alias, screen]) => [
              alias,
              `(min-width: ${screen})`,
            ])
          )
        ),
        switchMap((screens: Dictionary<string>) =>
          this._breakpointObserver.observe(Object.values(screens)).pipe(
            map((state) => {
              // Prepare the observable values and set their defaults
              const matchingAliases: string[] = [];
              const matchingQueries: any = {};

              // Get the matching breakpoints and use them to fill the subject
              const matchingBreakpoints =
                Object.entries(state.breakpoints).filter(
                  ([query, matches]) => matches
                ) ?? [];
              for (const [query] of matchingBreakpoints) {
                // Find the alias of the matching query
                if (!!Object.entries(screens)) {
                  const matchingAlias = Object.entries(screens).find(
                    ([alias, q]) => q === query
                  );

                  // Add the matching query to the observable values
                  if (!!matchingAlias && !!matchingAlias[0]) {
                    matchingAliases.push(matchingAlias[0]);
                    matchingQueries[matchingAlias[0]] = query;
                  }
                }
              }

              // Execute the observable
              this._onMediaChange.next({
                matchingAliases,
                matchingQueries,
              });
            })
          )
        )
      )
      .subscribe();
  }

  // -----------------------------------------------------------------------------------------------------
  // @ Accessors
  // -----------------------------------------------------------------------------------------------------

  /**
   * Getter for _onMediaChange
   */
  get onMediaChange$(): Observable<{
    matchingAliases: string[];
    matchingQueries: any;
  }> {
    return this._onMediaChange.asObservable();
  }

  // -----------------------------------------------------------------------------------------------------
  // @ Public methods
  // -----------------------------------------------------------------------------------------------------

  /**
   * On media query change
   *
   * @param query
   */
  onMediaQueryChange$(query: string | string[]): Observable<BreakpointState> {
    return this._breakpointObserver.observe(query);
  }
}
