import { distinctUntilChanged, filter, map, mergeMap, take, tap } from 'rxjs/operators';
import { merge as observableMerge, Observable, throwError as observableThrowError } from 'rxjs';
import { isEmpty, isNotEmpty } from '../../shared/empty.util';
import { NormalizedCommunity } from '../cache/models/normalized-community.model';
import { ObjectCacheService } from '../cache/object-cache.service';
import { ResponseCacheEntry } from '../cache/response-cache.reducer';
import { CommunityDataService } from './community-data.service';

import { DataService } from './data.service';
import { FindAllOptions, FindByIDRequest } from './request.models';
import { NormalizedObject } from '../cache/models/normalized-object.model';
import { HALEndpointService } from '../shared/hal-endpoint.service';

export abstract class ComColDataService<TNormalized extends NormalizedObject, TDomain> extends DataService<TNormalized, TDomain> {
  protected abstract cds: CommunityDataService;
  protected abstract objectCache: ObjectCacheService;
  protected abstract halService: HALEndpointService;

  /**
   * Get the scoped endpoint URL by fetching the object with
   * the given scopeID and returning its HAL link with this
   * data-service's linkPath
   *
   * @param {string} scopeID
   *    the id of the scope object
   * @return { Observable<string> }
   *    an Observable<string> containing the scoped URL
   */
  public getBrowseEndpoint(options: FindAllOptions = {}): Observable<string> {
    if (isEmpty(options.scopeID)) {
      return this.halService.getEndpoint(this.linkPath);
    } else {
      const scopeCommunityHrefObs = this.cds.getEndpoint().pipe(
        mergeMap((endpoint: string) => this.cds.getFindByIDHref(endpoint, options.scopeID)),
        filter((href: string) => isNotEmpty(href)),
        take(1),
        tap((href: string) => {
          const request = new FindByIDRequest(this.requestService.generateRequestId(), href, options.scopeID);
          this.requestService.configure(request);
        }));

      const responses = scopeCommunityHrefObs.pipe(
        mergeMap((href: string) => this.responseCache.get(href)),
        map((entry: ResponseCacheEntry) => entry.response));
      const errorResponses = responses.pipe(
        filter((response) => !response.isSuccessful),
        mergeMap(() => observableThrowError(new Error(`The Community with scope ${options.scopeID} couldn't be retrieved`)))
      );
      const successResponses = responses.pipe(
        filter((response) => response.isSuccessful),
        mergeMap(() => this.objectCache.getByUUID(options.scopeID)),
        map((nc: NormalizedCommunity) => nc._links[this.linkPath]),
        filter((href) => isNotEmpty(href))
      );

      return observableMerge(errorResponses, successResponses).pipe(distinctUntilChanged());
    }
  }
}