import { TaskQueue, autoinject } from 'aurelia-framework';
import { I18N } from 'aurelia-i18n';
import { GlobalLoaderService } from './global-loader-service';
import { HttpClient } from 'aurelia-fetch-client';
import { bindable } from 'aurelia-templating';
import { EventAggregator } from 'aurelia-event-aggregator';
import { observable } from 'aurelia-binding';
import { UIInternal } from '../../utils/ui-internal';

@autoinject
export class GlobalLoader {
  private startTimer;
  private doneTimer;
  private progressTimer;

  @bindable
  startDelay: string | number = 1000;

  @bindable
  public imgSrc: string;
  private imageEl: HTMLImageElement;
  private textEl: HTMLSpanElement;
  private progressEl: HTMLSpanElement;
  private progressValue: number = 0;
  private success = true;

  @observable
  private displayed = false;

  private pendingRequests: Array<string> = [];

  constructor(eventAggregator: EventAggregator, httpClient: HttpClient, private element: Element, public service: GlobalLoaderService, private i18n: I18N, private taskQueue: TaskQueue) {
    // eventAggregator.subscribe('router:navigation:processing', this.start);
    // eventAggregator.subscribe('router:navigation:complete', this.done);

    eventAggregator.subscribe('flow:uploading', (e) => this.start(e));
    eventAggregator.subscribe('flow:progress', this.progress);
    eventAggregator.subscribe('flow:complete', (e) => this.done(true, e?.url));

    UIInternal.subscribe(UIInternal.EVT_GLOBAL_LOADER_SHOW, () => this.show());
    UIInternal.subscribe(UIInternal.EVT_GLOBAL_LOADER_HIDE, () => this.hide());

    httpClient.configure(config => {
      config.withInterceptor({
        request: this.requestFn,
        requestError: this.requestErrorFn,
        response: this.responseFn,
        responseError: this.responseErrorFn
      });
    });
  }

  progress(value) {
    this.progressValue = value;
  }

  requestFn = (request: Request) => {
    this.start(request);
    return request;
  }

  requestErrorFn = (error) => {
    this.done(false, null);
    return error;
  }

  responseFn = (response: Response) => {
    this.done(response.status == 200, response.url);
    return response;
  }

  responseErrorFn = (error) => {
    this.done(false, null);
    return error;
  }

  private start(request: Request) {
    if (!this.service?.allowToShowGlobalLoader || (this.service?.forceShow ?? true)) return;
    if (this.startTimer)
      clearTimeout(this.startTimer);
    this.success = true;
    this.displayed = true;
    if (this.progressValue > 101)
      this.progressValue = 1;

    this.startTimer = setTimeout(() => {
      this.pendingRequests.push(request.url);
      this.show();
    }, (this.service?.delay != null ? this.service.delay : parseInt(this.startDelay.toString())));
  }

  private done(success = true, responseUrl: string) {
    if (this.service?.forceShow ?? true) return;
    this.doneTimer = setTimeout(async () => {
      this.clearPendingRequest(responseUrl);
      clearTimeout(this.startTimer);
      clearTimeout(this.progressTimer);
      if (this.displayed && (this.pendingRequests.length == 0 || !success)) {
        this.progressValue = 102;
        this.success = success;
        this.element.classList.add('done');
        await this.swapTextOff(success);
        this.displayed = false;
        this.success = true;
        this.hide();
        this.service.reset();
      }
    }, 100);
  }

  private clearPendingRequest(url: string) {
    let index = this.pendingRequests.findIndex(x => x = url);
    if (index > -1) {
      this.pendingRequests.splice(index, 1);
    }
  }

  private swapTextOff(success = true): Promise<boolean> {
    return new Promise<boolean>((res, rej) => {
      if (this.textEl != null && this.textEl.innerText?.trim().length > 0) {
        this.taskQueue.queueTask(() => {
          this.textEl.innerHTML = success ? this.i18n.tr('global-loader.done') : this.i18n.tr('global-loader.error');
          setTimeout(() => res(true), 800);
        });
      } else {
        setTimeout(() => res(true), 200);
      }
    });
  }

  private show() {
    (<HTMLElement>this.element)?.classList.add('show');
    this.initImage();
    this.initText();
    this.initProgress();
  }

  private hide() {
    this.taskQueue.queueTask(() => {
      (<HTMLElement>this.element)?.classList.remove('show');
      (<HTMLElement>this.element)?.classList.remove('progress');
      if (this.textEl) {
        this.textEl.classList.remove('off');
        this.textEl.innerHTML = "";
      }
      this.element.classList.remove('done');
    });
  }

  private initText(): void {
    if (this.service?.text?.trim().length == 0) return;
    if (!this.textEl) return;

    this.textEl.innerHTML = this.i18n.tr(this.service.text);
  }

  private initImage(): void {
    if (this.imgSrc == null) return;
    if (!this.imageEl) return;

    this.imageEl.src = this.imgSrc;
  }

  private initProgress(): void {
    this.progressTimer = setTimeout(() => {
      this.inc();
      if (this.progressValue < 102)
        this.initProgress();
    }, 200);
  }

  private inc(amount: number = null) {
    // if (this.progressValue == 100) return;
    if (this.progressValue == 101)
      this.progressValue = 1;

    var n = this.progressValue;

    if (n == null) {
      return null;
    } else if (n > 101) {
      return;
    } else {
      if (typeof amount !== 'number') {
        if (n >= 0 && n < 20) { amount = 3; }
        else if (n >= 20 && n < 40) { amount = 2; }
        else if (n >= 40 && n < 54) { amount = 1; }
        else if (n >= 54 && n < 80) { amount = 2; }
        else if (n >= 80 && n < 102) { amount = 1; }
        else { amount = 0; }
      }

      n = this.clamp(n + amount, 0, 101);
      return this.progressValue = n;
    }
  };

  private clamp(n, min, max) {
    if (n < min) return min;
    if (n > max) return max;
    return n;
  }
}
