import { Component, ChangeDetectionStrategy, ChangeDetectorRef, ViewChild, ElementRef, ViewEncapsulation, HostListener, inject } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { tap, take, map, catchError, switchMap, filter, shareReplay, retry } from 'rxjs/operators';
import { of, BehaviorSubject, Subject, combineLatest, zip } from 'rxjs';
import { State } from '@progress/kendo-data-query';
import { MessageService } from '../_shared/services/message.service';
import { Validators } from '@angular/forms';
import { DialogService, DialogSettings } from '@progress/kendo-angular-dialog';
import { NotifyService } from '../_shared/services/notify.service';
import { saveAs } from '@progress/kendo-file-saver';
import * as util from '../_shared/utils/util';
import { TransferMeterService, Item, Detail, SaveType, RequiredData, ZoneItem, MeterItem, PipelineDetail } from './transfer-meter.service';
import { CustomFormBuilder } from '../_shared/services/custom-form-builder.service';
import { FAST_KENDO_COMMON, FAST_PAGE_COMMON } from '../app.config';

@Component({
  selector: 'app-TransferMeter',
  templateUrl: './transfer-meter.component.html',
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [FAST_PAGE_COMMON, FAST_KENDO_COMMON]
})
export class TransferMeterComponent {
  private service = inject(TransferMeterService);
  private messageService = inject(MessageService);
  private titleService = inject(Title);
  private fb = inject(CustomFormBuilder);
  private ref = inject(ChangeDetectorRef);
  private dialogService = inject(DialogService);
  private notify = inject(NotifyService);

  @ViewChild("grid", { read: ElementRef }) kendoGridEl: ElementRef;
  @HostListener('window:resize') onResize() {
    //this function is empty but for some reason it helps the window to resize faster
  };

  constructor() {
  }

  util = util;

  detailForm = this.getDetailForm();
  gridScrollPosition: util.GridScrollPosition = { topPos: 0, leftPos: 0 };
  localRequiredData: RequiredData;
  hasModifyPermission = false;
  detailInitialValues: Detail = this.detailForm.value as Detail;
  mySelection: number[] = [];
  manualChangesTriggering: boolean = false;

  gridLoading$ = new BehaviorSubject<boolean>(true)
  refreshItems$ = new BehaviorSubject<string>(null)
  exporting$ = new BehaviorSubject<boolean>(false)
  detailOpened$ = new BehaviorSubject<boolean>(false)
  refreshRequiredData$ = new BehaviorSubject(null)
  detailLoading$ = new BehaviorSubject<boolean>(true)
  refreshDetail$ = new BehaviorSubject<number>(null)
  save$ = new Subject<SaveType>()
  delete$ = new Subject()
  exportClicked$ = new Subject()

  state: State = {
    filter: null,
    group: null,
    skip: 0,
    sort: [{ field: 'Pipe1Name', dir: 'asc' }],
    take: 50
  };

  getDetailForm() {
    const fb = this.fb;
    const fg: util.FormModel<Detail> = fb.group({
      transferMeterId: fb.ctr(0, Validators.required),
      productId: fb.ctr(1, Validators.required),  // set default product to Natural Gas
      pipe1Id: fb.ctr(null, Validators.required),
      pipe1Name: fb.ctr(null),
      zone1Id: fb.ctr(null, Validators.required),
      zone1Name: fb.ctr(null),
      meter1Id: fb.ctr(null, Validators.required),
      meter1Name: fb.ctr(null),
      pipe2Id: fb.ctr(null, Validators.required),
      pipe2Name: fb.ctr(null),
      zone2Id: fb.ctr(null, Validators.required),
      zone2Name: fb.ctr(null),
      meter2Id: fb.ctr(null, Validators.required),
      meter2Name: fb.ctr(null)
    });

    return fg;
  }

  requiredData$ = this.refreshRequiredData$.pipe(
    tap(() => this.detailLoading$.next(true)),
    switchMap(refreshType => {
      return combineLatest([this.service.requiredData$, of(refreshType)]);
    }),
    map(([requiredData, refreshType]) => {
      this.localRequiredData = requiredData;
      if (refreshType === util.RefreshType.SelfOnly)
        this.detailLoading$.next(false);
      return requiredData;
    }),
    tap((requiredData) => {
      this.hasModifyPermission = requiredData.hasModifyPermission;
    }),
    shareReplay(1),
    catchError(err => {
      return util.handleError(err, this.messageService)
    }), retry(10)
  )

  items$ = this.refreshItems$.pipe(
    tap(() => {
      this.gridLoading$.next(true);
    }),
    switchMap(() => {
      return this.service.getItems(this.state);
    }),
    tap(() => {
      this.gridLoading$.next(false);
      util.goToSavedGridScrollPos(this.kendoGridEl, this.gridScrollPosition);
    }),
    shareReplay(1),
    catchError(err => {
      return util.handleError(err, this.messageService);
    }), retry(10)
  )

  exportAction$ = this.exportClicked$.pipe(
    tap(() => {
      this.exporting$.next(true);
    }),
    switchMap(() => {
      return this.service.exportItems(this.state, 'TransferMeters.xlsx');
    }),
    tap(res => {
      saveAs(res.fileBlob, res.fileName);
      this.exporting$.next(false);
    }),
    shareReplay(1),
    catchError(err => {
      this.exporting$.next(false);
      return util.handleError(err, this.messageService);
    }), retry(10)
  )

  //this updates localDetailLoading whenever detailLoading$ changes so that we can check the loading status in this class
  localDetailLoading = true;
  detailLoadingWatcher$ = this.detailLoading$.pipe(
    tap(isLoading => {
      this.localDetailLoading = isLoading;
    }),
    shareReplay(1)
  )

  detail$ = this.refreshDetail$.pipe(
    filter(id => id !== null),
    switchMap(id => {
      this.detailLoading$.next(true);
      this.detailForm.disable();
      this.detailForm.reset();
      if (id === 0) {
        return of(this.detailInitialValues);
      }
      else
        return this.service.getDetail(id);
    }),
    map(result => {
      const detail: Detail = result;
      if (detail) {
        util.convertToDates(detail);
        this.detailForm.setValue(detail);
      }
      return detail;
    }),
    tap(() => {
      this.detailFinishedLoading();
    }),
    shareReplay(1),
    catchError(err => {
      this.closeDetail(false);
      return util.handleError(err, this.messageService)
    }), retry(10)
  )

  saveResult$ = this.save$.pipe(
    switchMap(saveType => {
      this.detailLoading$.next(true);
      this.detailForm.disable();
      const itemToSave: Detail = this.detailForm.value as Detail;
      return this.service.saveDetail(itemToSave, saveType);
    }),
    tap(saveResult => {
      this.notify.success('save successful');
      this.closeDetail(false);
      this.refreshItems$.next(null);
      this.mySelection = [saveResult];
    }),
    shareReplay(1),
    catchError(err => {
      this.detailFinishedLoading();
      return util.handleError(err, this.messageService)
    }), retry(10)
  )

  deleteResult$ = this.delete$.pipe(
    switchMap(() => {
      this.detailLoading$.next(true);
      this.detailForm.disable();
      const itemToDelete: Detail = this.detailForm.getRawValue();
      return this.service.deleteDetail(itemToDelete.transferMeterId);
    }),
    tap(() => {
      this.notify.success('delete successful');
      this.detailFinishedLoading();
      this.closeDetail(false);
      this.refreshItems$.next(null);
    }),
    shareReplay(1),
    catchError(err => {
      return util.handleError(err, this.messageService)
    }), retry(10)
  )

  openDetail(id: number): void {
    util.saveGridScrollPos(this.kendoGridEl, this.gridScrollPosition);
    this.refreshDetail$.next(id);
    this.detailOpened$.next(true);
  }

  onDetailClosing() {
    util.onDetailChanging(this.detailForm, this.dialogService, this.closeDetail, this.save);
  }

  closeDetail = (isFromInterface: boolean) => {
    this.detailOpened$.next(false);

    if (isFromInterface)
      util.goToSavedGridScrollPos(this.kendoGridEl, this.gridScrollPosition);
  }

  detailFinishedLoading(): void {
    this.manualChangesTriggering = true;
    this.refreshPipesForProduct1$.next(null);
    this.refreshPipesForProduct2$.next(null);
    this.refreshZonesForPipe1$.next(null);
    this.refreshZonesForPipe2$.next(null);
  }

  add(): void {
    this.openDetail(0);
  }

  edit(dataItem: Item): void {
    this.openDetail(dataItem.TransferMeterId);
  }

  save = (saveType: util.SaveType) => {
    this.detailForm.markAllAsTouched();
    if (this.detailForm.valid)
      this.save$.next(saveType);
    else
      this.notify.error("validation failed");
  }

  delete(): void {
    const deleteConfirmSettings: DialogSettings = {
      title: "Please confirm",
      content: "Are you sure you want to delete this item?",
      actions: [{ text: 'No' }, { text: 'Yes', cssClass: 'k-primary' }],
      cssClass: 'utilPrompt'
    }

    this.dialogService.open(deleteConfirmSettings).result.pipe(take(1)).subscribe(result => {
      if (util.getDialogAction(result) === util.dialogAction.Yes)
        this.delete$.next(null);
    });
  }

  export(): void {
    this.exportClicked$.next(null);
  }

  dataStateChange(state: State): void {
    this.gridScrollPosition.topPos = 0;
    this.gridScrollPosition.leftPos = 0;
    util.fixKendoQueryFilter(state.filter);
    this.state = state;
    this.refreshItems$.next(null);
  }

  refreshDropdowns() {
    this.refreshRequiredData$.next(util.RefreshType.SelfOnly);
  }

  title$ = of('Transfer Meter Map').pipe(
    tap((title) => util.trySetTitle(this.titleService, title))
  )

  productChanged$ = this.detailForm.get('productId').valueChanges.pipe(
    filter(() => {
      return !this.localDetailLoading || this.manualChangesTriggering;
    }),
    tap(() => {
      this.refreshPipesForProduct1$.next(null);
      this.refreshPipesForProduct2$.next(null);
    })
  )

  refreshPipesForProduct1$ = new Subject<string>()
  subPipesForProduct1$ = this.refreshPipesForProduct1$.pipe(
    filter(() => {
      return !this.localDetailLoading || this.manualChangesTriggering;
    }),
    map(() => {
      const productId: number = this.detailForm.get('productId').value;
      let subPipes: PipelineDetail[] = [];

      if (productId) {
        subPipes = this.localRequiredData.pipelines.filter(pipelineDetail => {
          return pipelineDetail.productIds.includes(productId);
        });
      }
      const pipe1Id = this.detailForm.get('pipe1Id').value;
      if (subPipes && subPipes.findIndex(x => x.pipelineId === pipe1Id) === -1)
        this.detailForm.patchValue({ pipe1Id: null }, { emitEvent: false });

      if (!this.localDetailLoading && subPipes && subPipes.length === 1)
        this.detailForm.patchValue({ pipe1Id: subPipes[0].pipelineId }, { emitEvent: false });

      return subPipes;
    }),
    tap(() => {
      this.refreshZonesForPipe1$.next(null);
    }),
    shareReplay(1)
  )

  refreshPipesForProduct2$ = new Subject<string>()
  subPipesForProduct2$ = this.refreshPipesForProduct2$.pipe(
    filter(() => {
      return !this.localDetailLoading || this.manualChangesTriggering;
    }),
    map(() => {
      const productId: number = this.detailForm.get('productId').value;
      let subPipes: PipelineDetail[] = [];

      if (productId) {
        subPipes = this.localRequiredData.pipelines.filter(pipelineDetail => {
          return pipelineDetail.productIds.includes(productId);
        });
      }
      const pipe2Id = this.detailForm.get('pipe2Id').value;
      if (subPipes && subPipes.findIndex(x => x.pipelineId === pipe2Id) === -1)
        this.detailForm.patchValue({ pipe2Id: null }, { emitEvent: false });

      if (!this.localDetailLoading && subPipes && subPipes.length === 1)
        this.detailForm.patchValue({ pipe2Id: subPipes[0].pipelineId }, { emitEvent: false });

      return subPipes;
    }),
    tap(() => {
      this.refreshZonesForPipe2$.next(null);
    }),
    shareReplay(1)
  )

  pipeline1Changed$ = this.detailForm.get('pipe1Id').valueChanges.pipe(
    filter(() => {
      return !this.localDetailLoading || this.manualChangesTriggering;
    }),
    tap(() => {
      this.refreshZonesForPipe1$.next(null);
    })
  )

  refreshZonesForPipe1$ = new Subject<string>()
  subZonesForPipe1Id$ = this.refreshZonesForPipe1$.pipe(
    filter(() => {
      return !this.localDetailLoading || this.manualChangesTriggering;
    }),
    map(() => {
      const pipe1Id: number = this.detailForm.get('pipe1Id').value;
      let subZones: ZoneItem[] = [];

      if (pipe1Id) {
        subZones = this.localRequiredData.zones.filter(zoneItem => {
          return zoneItem.pipeId === pipe1Id;
        });
      }
      const zone1Id = this.detailForm.get('zone1Id').value;
      if (subZones && subZones.findIndex(x => x.zoneId === zone1Id) === -1)
        this.detailForm.patchValue({ zone1Id: null }, { emitEvent: false });

      if (!this.localDetailLoading && subZones && subZones.length === 1)
        this.detailForm.patchValue({ zone1Id: subZones[0].zoneId }, { emitEvent: false });

      return subZones;
    }),
    tap(() => {
      this.RefreshMetersForZone1$.next(null);
    }),
    shareReplay(1)
  )

  zone1Changed$ = this.detailForm.get('zone1Id').valueChanges.pipe(
    filter(() => {
      return !this.localDetailLoading || this.manualChangesTriggering;
    }),
    tap(() => {
      this.RefreshMetersForZone1$.next(null);
    })
  )

  RefreshMetersForZone1$ = new Subject<string>()
  subMetersForZone1Id$ = this.RefreshMetersForZone1$.pipe(
    filter(() => {
      return !this.localDetailLoading || this.manualChangesTriggering;
    }),
    map(() => {
      const zone1Id: number = this.detailForm.get('zone1Id').value;
      let subMeters: MeterItem[] = [];

      if (zone1Id) {
        subMeters = this.localRequiredData.meters.filter(meterItem => {
          return meterItem.zoneId === zone1Id;
        });
      }
      const meter1Id = this.detailForm.get('meter1Id').value;
      if (subMeters && subMeters.findIndex(x => x.meterId === meter1Id) === -1)
        this.detailForm.patchValue({ meter1Id: null }, { emitEvent: false });

      if (!this.localDetailLoading && subMeters && subMeters.length === 1)
        this.detailForm.patchValue({ meter1Id: subMeters[0].meterId }, { emitEvent: false });

      return subMeters;
    }),
    shareReplay(1)
  )

  pipeline2Changed$ = this.detailForm.get('pipe2Id').valueChanges.pipe(
    filter(() => {
      return !this.localDetailLoading || this.manualChangesTriggering;
    }),
    tap(() => {
      this.refreshZonesForPipe2$.next(null);
    })
  )

  refreshZonesForPipe2$ = new Subject<string>()
  subZonesForPipe2Id$ = this.refreshZonesForPipe2$.pipe(
    filter(() => {
      return !this.localDetailLoading || this.manualChangesTriggering;
    }),
    map(() => {
      const pipe2Id: number = this.detailForm.get('pipe2Id').value;
      let subZones: ZoneItem[] = [];

      if (pipe2Id) {
        subZones = this.localRequiredData.zones.filter(zoneItem => {
          return zoneItem.pipeId === pipe2Id;
        });
      }
      const zone2Id = this.detailForm.get('zone2Id').value;
      if (subZones && subZones.findIndex(x => x.zoneId === zone2Id) === -1)
        this.detailForm.patchValue({ zone2Id: null }, { emitEvent: false });

      if (!this.localDetailLoading && subZones && subZones.length === 1)
        this.detailForm.patchValue({ zone2Id: subZones[0].zoneId }, { emitEvent: false });

      return subZones;
    }),
    tap(() => {
      this.RefreshMetersForZone2$.next(null);
    }),
    shareReplay(1)
  )

  zone2Changed$ = this.detailForm.get('zone2Id').valueChanges.pipe(
    filter(() => {
      return !this.localDetailLoading || this.manualChangesTriggering;
    }),
    tap(() => {
      this.RefreshMetersForZone2$.next(null);
    })
  )

  RefreshMetersForZone2$ = new Subject<string>()
  subMetersForZone2Id$ = this.RefreshMetersForZone2$.pipe(
    filter(() => {
      return !this.localDetailLoading || this.manualChangesTriggering;
    }),
    map(() => {
      const zone2Id: number = this.detailForm.get('zone2Id').value;
      let subMeters: MeterItem[] = [];

      if (zone2Id) {
        subMeters = this.localRequiredData.meters.filter(meterItem => {
          return meterItem.zoneId === zone2Id;
        });
      }
      const meter2Id = this.detailForm.get('meter2Id').value;
      if (subMeters && subMeters.findIndex(x => x.meterId === meter2Id) === -1)
        this.detailForm.patchValue({ meter2Id: null }, { emitEvent: false });

      if (!this.localDetailLoading && subMeters && subMeters.length === 1)
        this.detailForm.patchValue({ meter2Id: subMeters[0].meterId }, { emitEvent: false });

      return subMeters;
    }),
    shareReplay(1)
  )

  manualTriggersCompleting$ = zip(this.refreshPipesForProduct1$, this.refreshPipesForProduct2$, this.refreshZonesForPipe1$, this.refreshZonesForPipe2$, this.RefreshMetersForZone1$, this.RefreshMetersForZone2$).pipe(
    filter(() => this.manualChangesTriggering),
    tap(() => {
      this.manualChangesTriggering = false;
      this.detailForm.enable();
      this.detailLoading$.next(false);
      util.focusAndSelectInputTarget();
    })
  )

  filterProducts$ = new BehaviorSubject<string>(null)
  products$ = this.filterProducts$.pipe(util.filterIdNames(this.detailLoading$, this.requiredData$, 'products'));

  filterPipelines1$ = new BehaviorSubject<string>(null)
  pipelines1$ = this.filterPipelines1$.pipe(util.filterSpecials(this.detailLoading$, this.subPipesForProduct1$, null, 'fullName'));

  filterZones1$ = new BehaviorSubject<string>(null)
  zones1$ = this.filterZones1$.pipe(util.filterSpecials(this.detailLoading$, this.subZonesForPipe1Id$, null, 'zoneName'));

  filterMeters1$ = new BehaviorSubject<string>(null)
  meters1$ = this.filterMeters1$.pipe(util.filterSpecials(this.detailLoading$, this.subMetersForZone1Id$, null, 'meterName'));

  filterPipelines2$ = new BehaviorSubject<string>(null)
  pipelines2$ = this.filterPipelines2$.pipe(util.filterSpecials(this.detailLoading$, this.subPipesForProduct2$, null, 'fullName'));

  filterZones2$ = new BehaviorSubject<string>(null)
  zones2$ = this.filterZones2$.pipe(util.filterSpecials(this.detailLoading$, this.subZonesForPipe2Id$, null, 'zoneName'));

  filterMeters2$ = new BehaviorSubject<string>(null)
  meters2$ = this.filterMeters2$.pipe(util.filterSpecials(this.detailLoading$, this.subMetersForZone2Id$, null, 'meterName'));
}
