import {
  Component,
  Injector,
  OnInit,
} from '@angular/core';
import { Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import {
  Observable,
  of,
} from 'rxjs';
import {
  map,
  switchMap,
  take,
  tap,
} from 'rxjs/operators';

import { IdentifiableDataService } from '../../core/data/base/identifiable-data.service';
import { RemoteData } from '../../core/data/remote-data';
import { RequestService } from '../../core/data/request.service';
import { DSpaceObject } from '../../core/shared/dspace-object.model';
import { getFirstCompletedRemoteData } from '../../core/shared/operators';
import { ResourceType } from '../../core/shared/resource-type';
import { SearchService } from '../../core/shared/search/search.service';
import { ProcessTaskResponse } from '../../core/tasks/models/process-task-response';
import { NotificationOptions } from '../notifications/models/notification-options.model';
import { NotificationsService } from '../notifications/notifications.service';
import { getSearchResultFor } from '../search/search-result-element-decorator';
import { MyDSpaceActionsComponent } from './mydspace-actions';

/**
 * Abstract class for all different representations of mydspace actions
 */
@Component({
  selector: 'ds-mydspace-reloadable-actions',
  standalone: true,
  template: '',
})
export abstract class MyDSpaceReloadableActionsComponent<T extends DSpaceObject, TService extends IdentifiableDataService<T>>
  extends MyDSpaceActionsComponent<T, TService> implements OnInit {

  protected constructor(
    protected objectType: ResourceType,
    protected injector: Injector,
    protected router: Router,
    protected notificationsService: NotificationsService,
    protected translate: TranslateService,
    protected searchService: SearchService,
    protected requestService: RequestService,
  ) {
    super(objectType, injector, router, notificationsService, translate, searchService, requestService);
  }

  /**
   * Perform the actual implementation of this reloadable action.
   */
  abstract actionExecution(): Observable<ProcessTaskResponse>;

  /**
   * Reload the object (typically by a remote call).
   */
  abstract reloadObjectExecution(): Observable<RemoteData<DSpaceObject> | DSpaceObject>;

  ngOnInit() {
    this.initReloadAnchor();
    this.initObjects(this.object);
  }

  /**
   * Start the execution of the action.
   * 1. performAction
   * 2. reload of the object
   * 3. notification
   */
  startActionExecution(): Observable<DSpaceObject> {
    this.processing$.next(true);
    return this.actionExecution().pipe(
      take(1),
      switchMap((res: ProcessTaskResponse) => {
        if (res.hasSucceeded) {
          return this._reloadObject().pipe(
            tap(
              (reloadedObject) => {
                this.processing$.next(false);
                this.handleReloadableActionResponse(res.hasSucceeded, reloadedObject);
              }),
          );
        } else {
          this.processing$.next(false);
          this.handleReloadableActionResponse(res.hasSucceeded, null);
          return of(null);
        }
      }));
  }

  /**
   * Handle the action response and show properly notifications.
   *
   * @param result
   *    true on success, false otherwise
   * @param reloadedObject
   *    the reloadedObject
   */
  handleReloadableActionResponse(result: boolean, reloadedObject: DSpaceObject): void {
    if (result) {
      if (reloadedObject) {
        this.processCompleted.emit({ result, reloadedObject });
        // Ensure that next time the page is requested the objects have the correct render type.
        this.invalidateCacheForCurrentSearchUrl();
      } else {
        this.reload();
      }
      this.notificationsService.success(null,
        this.translate.get('submission.workflow.tasks.generic.success'),
        new NotificationOptions(5000, false));
    } else {
      this.notificationsService.error(null,
        this.translate.get('submission.workflow.tasks.generic.error'),
        new NotificationOptions(20000, true));
    }
  }

  /**
   * Hook called on init to initialized the required information used to reload the object.
   */
  // eslint-disable-next-line no-empty, @typescript-eslint/no-empty-function
  initReloadAnchor() {}

  /**
   * Convert the reloadedObject to the Type required by this action.
   * @param dso
   */
  convertReloadedObject(dso: DSpaceObject): DSpaceObject {
    const constructor = getSearchResultFor((dso as any).constructor);
    const reloadedObject = Object.assign(new constructor(), dso, {
      indexableObject: dso,
    });
    return reloadedObject;
  }

  /**
   * Retrieve the refreshed object and transform it to a reloadedObject.
   * @param dso
   */
  private _reloadObject(): Observable<DSpaceObject> {
    return this.reloadObjectExecution().pipe(
      switchMap((res) => {
        if (res instanceof RemoteData) {
          return of(res).pipe(getFirstCompletedRemoteData(), map((completed) => completed.payload));
        } else {
          return of(res);
        }
      })).pipe(map((dso) => {
      return dso ? this.convertReloadedObject(dso) : dso;
    }));
  }

}