import { Component, ChangeDetectionStrategy, ChangeDetectorRef, ViewChild, ElementRef, ViewEncapsulation, ViewChildren, QueryList } 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, process } from '@progress/kendo-data-query';
import { PipeRateScheduleService, Detail, Item, RequiredData, Zone, Meter, DetailRate, DetailDiscount, DetailRateData } from './pipe-rate-schedule.service';
import { MessageService } from '../_shared/services/message.service';
import { FormArray, Validators } from '@angular/forms';
import { DialogService, DialogSettings, KENDO_DIALOG } from '@progress/kendo-angular-dialog';
import { NotifyService } from '../_shared/services/notify.service';
import { TooltipDirective } from '@progress/kendo-angular-tooltip';
import { GridDataResult, SelectableSettings } from '@progress/kendo-angular-grid';
import * as util from '../_shared/utils/util';
import { SuccessEvent, ErrorEvent, UploadComponent, UploadProgressEvent } from '@progress/kendo-angular-upload';
import { NumericTextBoxComponent } from '@progress/kendo-angular-inputs';
import { CustomFormBuilder } from '../_shared/services/custom-form-builder.service';
import { FAST_KENDO_COMMON, FAST_PAGE_COMMON } from '../app.config';

@Component({
  selector: 'app-pipe-rate-schedule',
  templateUrl: './pipe-rate-schedule.component.html',
  styleUrls: ['./pipe-rate-schedule.component.scss'],
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [FAST_PAGE_COMMON, FAST_KENDO_COMMON, KENDO_DIALOG]
})
export class PipeRateScheduleComponent {
  @ViewChild("grid", { read: ElementRef }) kendoGridEl: ElementRef;
  //even though there is only one rateGrid, we use ViewChildren since ViewChild does not work with *ngIf
  @ViewChildren("rateGrid", { read: ElementRef }) kendoRateGridEls: QueryList<ElementRef>;
  @ViewChild('tooltipGrid') tooltipGrid: TooltipDirective;
  @ViewChild('tooltipRateGrid') tooltipRateGrid: TooltipDirective;
  @ViewChild('tooltipDiscountGrid') tooltipDiscountGrid: TooltipDirective;
  @ViewChild('docRateUploadTarget') docRateUploadElem: UploadComponent;
  @ViewChild('docDiscountUploadTarget') docDiscountUploadElem: UploadComponent;
  @ViewChildren('detailRateElem') detailRateElems: QueryList<NumericTextBoxComponent>;

  util = util;
  icons = util.icons;
  hasModifyPermission = false;
  pipeSelectForm = this.getPipeSelectForm();
  detailForm = this.getDetailForm();
  detailRateForm = this.getDetailRateForm();
  detailRateDataFormArray: FormArray;
  detailDiscountForm = this.getDetailDiscountForm();
  detailInitialValues: Detail = this.detailForm.value as Detail;
  detailRateInitialValues: DetailRate = this.detailRateForm.value as DetailRate;
  detailDiscountInitialValues: DetailDiscount = this.detailDiscountForm.value as DetailDiscount;
  localRequiredData: RequiredData;
  gridScrollPosition: util.GridScrollPosition = { topPos: 0, leftPos: 0 };
  rateGridScrollPosition: util.GridScrollPosition = { topPos: 0, leftPos: 0 };
  mySelection: number[] = [];
  myRateSelection: number[] = [];
  myDiscountSelection: number[] = [];
  manualChangesTriggering: boolean = false;
  docRateSaveUrl = `${window.location.origin}/api/PipeRateSchedule/UploadDoc?rateDocType=1`;
  docDiscountSaveUrl = `${window.location.origin}/api/PipeRateSchedule/UploadDoc?rateDocType=2`;
  rateGridData: GridDataResult;
  discountGridData: GridDataResult;
  pipeName: string;
  scheduleName: string;
  contractTypeName: string;
  productName: string;

  gridLoading$ = new BehaviorSubject<boolean>(true)
  exporting$ = new BehaviorSubject<boolean>(false)
  detailLoading$ = new BehaviorSubject<boolean>(true);
  detailOpened$ = new BehaviorSubject<boolean>(false)
  detailRateOpened$ = new BehaviorSubject<boolean>(false)
  detailDiscountOpened$ = new BehaviorSubject<boolean>(false)
  pipeSelectOpened$ = new BehaviorSubject<boolean>(false)
  refreshItems$ = new BehaviorSubject<string>(null)
  refreshDetail$ = new BehaviorSubject<number>(null)
  refreshRequiredData$ = new BehaviorSubject(util.RefreshType.WithOthers)
  save$ = new Subject<util.SaveType>()
  delete$ = new Subject()
  exportClicked$ = new Subject()
  downloadDoc$ = new Subject<[util.DocItem, number]>()

  selectableSettings: SelectableSettings = {
    checkboxOnly: false,
    mode: 'single',
    enabled: true
  }

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

  rateState: State = {
    filter: null,
    sort: [{ field: 'rateType', dir: 'asc' }, { field: 'effectiveDate', dir: 'desc' }]
  };

  discountState: State = {
    filter: null,
    sort: [{ field: 'rateType', dir: 'asc' }, { field: 'effectiveDate', dir: 'desc' }, { field: 'expirationDate', dir: 'desc' }]
  };

  constructor(private messageService: MessageService, private titleService: Title, private service: PipeRateScheduleService, private fb: CustomFormBuilder, private ref: ChangeDetectorRef, private dialogService: DialogService, private notify: NotifyService) {

  }

  getPipeSelectForm() {
    const form = {
      pipelineId: [null, Validators.required],
      productId: [null, Validators.required]
    }

    return this.fb.group(form);
  }

  getDetailForm() {
    const fb = this.fb;
    const fg: util.FormModel<Detail> = fb.group({
      id: fb.ctr(0, Validators.required),
      scheduleName: fb.ctr(null, Validators.required),
      pipelineId: fb.ctr(null, Validators.required),
      contractTypeId: fb.ctr(1, Validators.required),
      rates: fb.ctr([]),
      discounts: fb.ctr([]),
      productId: fb.ctr(null, Validators.required),
    });

    return fg;
  }

  getDetailRateForm() {
    const fb = this.fb;
    const fg: util.FormModel<DetailRate> = fb.group({
      id: fb.ctr(0, Validators.required),
      rateTypeId: fb.ctr(null, Validators.required),
      effectiveDate: fb.ctr(util.currentDate.toDate(), Validators.required),
      applicationRuleId: fb.ctr(1, Validators.required),
      singleRate: fb.ctr(null),
      documents: fb.ctr([]),
      notes: fb.ctr(null),
      multiRateData: fb.arr([]),
      rateType: fb.ctr(null),
      applicationRule: fb.ctr(null)
    });

    return fg;
  }

  getDetailDiscountForm() {
    const fb = this.fb;
    const fg: util.FormModel<DetailDiscount> = fb.group({
      id: fb.ctr(0, Validators.required),
      rateTypeId: fb.ctr(null, Validators.required),
      effectiveDate: fb.ctr(util.currentDate.toDate(), Validators.required),
      expirationDate: fb.ctr(null),
      rate: fb.ctr(null, Validators.required),
      fromZoneIds: fb.ctr([]),
      fromMeterIds: fb.ctr([]),
      toZoneIds: fb.ctr([]),
      toMeterIds: fb.ctr([]),
      counterpartyIds: fb.ctr([]),
      documents: fb.ctr([]),
      notes: fb.ctr(null),
      rateType: fb.ctr(null),
      fromZones: fb.ctr(null),
      fromMeters: fb.ctr(null),
      toZones: fb.ctr(null),
      toMeters: fb.ctr(null),
      counterparties: fb.ctr(null),
    });

    return fg;
  }

  title$ = of('Pipeline Rate Schedule').pipe(
    tap((title) => util.trySetTitle(this.titleService, title))
  )

  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.refreshPipeForProduct$.next(null);
      this.hasModifyPermission = requiredData.hasModifyPermission;
      util.focusInputTarget();
    }),
    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(
    switchMap(() => {
      this.exporting$.next(true);
      return this.service.exportItems(this.state, 'Pipeline Rate Schedule.xlsx');
    }),
    tap(res => {
      util.openOrSaveFile(res.fileBlob, res.fileName);
      this.exporting$.next(false);
    }),
    shareReplay(1),
    catchError(err => {
      this.exporting$.next(false);
      return util.handleError(err, this.messageService)
    }), retry(10)
  )

  detail$ = this.refreshDetail$.pipe(
    filter(id => id !== null),
    switchMap(id => {
      this.detailLoading$.next(true);
      this.detailForm.disable();
      this.detailForm.reset();
      this.detailRateForm.reset();
      this.detailDiscountForm.reset();
      if (id === 0) {
        this.detailInitialValues.pipelineId = this.pipeSelectForm.get('pipelineId').value;
        this.detailInitialValues.productId = this.pipeSelectForm.get('productId').value;
        return of(this.detailInitialValues);
      }
      else
        return this.service.getDetail(id);
    }),
    map(result => {
      const detail: Detail = new Detail(result, this.localRequiredData);
      if (detail) {
        util.convertToDates(detail, ['startMonth', 'endMonth']);
        this.detailForm.setValue(detail);
        this.setReadOnlyNames();
        this.rateGridData = process(detail.rates, this.rateState);
        this.discountGridData = process(detail.discounts, this.discountState);
      }
      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() as Detail;
      return this.service.deleteDetail(itemToDelete.id);
    }),
    tap(() => {
      this.notify.success('delete successful');
      this.detailFinishedLoading();
      this.closeDetail(false);
      this.refreshItems$.next(null);
    }),
    shareReplay(1),
    catchError(err => {
      this.detailLoading$.next(false);
      this.detailForm.enable();
      return util.handleError(err, this.messageService)
    }), retry(10)
  )

  pipelineChanged$ = this.detailForm.get('pipelineId').valueChanges.pipe(
    filter(() => !this.detailLoading$.value || this.manualChangesTriggering),
    tap(() => {
      this.refreshFromZones$.next(null);
      this.refreshToZones$.next(null);
    }),
    shareReplay(1),
  )

  detailRateTypeChanged$ = this.detailRateForm.get('rateTypeId').valueChanges.pipe(
    filter(() => !this.detailLoading$.value || this.manualChangesTriggering),
    tap((newRateTypeId: number) => {
      this.updateFormControlStates(newRateTypeId);
    }),
    shareReplay(1),
    catchError(err => {
      return util.handleError(err, this.messageService)
    }), retry(1)
  )

  getSelectedIdsWithAllItemCorrection(selectedIds: number[]): number[] {
    if (selectedIds && selectedIds.length > 0) {
      const allItemSelected = selectedIds[selectedIds.length - 1] === 0;
      if (allItemSelected)
        selectedIds = selectedIds = [0]; //removes any items that are not ALL
      else
        selectedIds = selectedIds.filter(x => x > 0); //removes the ALL item
    }
    return selectedIds
  }

  productChanged$ = this.pipeSelectForm.get('productId').valueChanges.pipe(
    filter(() => !this.gridLoading$.value || this.manualChangesTriggering),
    tap(() => {
      this.refreshPipeForProduct$.next(null);
    }),
    shareReplay(1)
  )

  fromZonesChanged$ = this.detailDiscountForm.get('fromZoneIds').valueChanges.pipe(
    filter(() => !this.detailLoading$.value || this.manualChangesTriggering),
    tap((selectedIds: number[]) => {
      selectedIds = this.getSelectedIdsWithAllItemCorrection(selectedIds);
      this.detailDiscountForm.patchValue({ fromZoneIds: selectedIds }, { emitEvent: false });
      this.refreshFromMeters$.next(null);
    }),
    shareReplay(1)
  )

  toZonesChanged$ = this.detailDiscountForm.get('toZoneIds').valueChanges.pipe(
    filter(() => !this.detailLoading$.value || this.manualChangesTriggering),
    tap((selectedIds: number[]) => {
      selectedIds = this.getSelectedIdsWithAllItemCorrection(selectedIds);
      this.detailDiscountForm.patchValue({ toZoneIds: selectedIds }, { emitEvent: false });
      this.refreshToMeters$.next(null);
    }),
    shareReplay(1)
  )

  fromMetersChanged$ = this.detailDiscountForm.get('fromMeterIds').valueChanges.pipe(
    filter(() => !this.detailLoading$.value || this.manualChangesTriggering),
    tap((selectedIds: number[]) => {
      selectedIds = this.getSelectedIdsWithAllItemCorrection(selectedIds);
      this.detailDiscountForm.patchValue({ fromMeterIds: selectedIds }, { emitEvent: false });
    }),
    shareReplay(1)
  )

  toMetersChanged$ = this.detailDiscountForm.get('toMeterIds').valueChanges.pipe(
    filter(() => !this.detailLoading$.value || this.manualChangesTriggering),
    tap((selectedIds: number[]) => {
      selectedIds = this.getSelectedIdsWithAllItemCorrection(selectedIds);
      this.detailDiscountForm.patchValue({ toMeterIds: selectedIds }, { emitEvent: false });
    }),
    shareReplay(1)
  )

  counterpartiesChanged$ = this.detailDiscountForm.get('counterpartyIds').valueChanges.pipe(
    filter(() => !this.detailLoading$.value || this.manualChangesTriggering),
    tap((selectedIds: number[]) => {
      selectedIds = this.getSelectedIdsWithAllItemCorrection(selectedIds);
      this.detailDiscountForm.patchValue({ counterpartyIds: selectedIds }, { emitEvent: false });
    }),
    shareReplay(1)
  )

  refreshPipeForProduct$ = new Subject<string>()
  pipeForProduct$ = this.refreshPipeForProduct$.pipe(
    filter(() => !this.gridLoading$.value || this.manualChangesTriggering),
    map(() => {
      const productId = this.pipeSelectForm.get('productId').value;
      const pipelines = this.localRequiredData.pipelines;

      if (!productId)
        return [];

      const newPipes = pipelines.filter(x => x.productIds.includes(productId));
      return newPipes;
    }),
    shareReplay(1)
  )

  refreshFromZones$ = new Subject<string>()
  fromZonesForPipe$ = this.refreshFromZones$.pipe(
    filter(() => !this.detailLoading$.value || this.manualChangesTriggering),
    map(() => {
      const pipelineId: number = this.detailForm.get('pipelineId').value;
      let subZones: Zone[] = [];

      if (pipelineId) {
        subZones = this.localRequiredData.zones.filter(zone => {
          return zone.pipelineId === pipelineId || zone.pipelineId === 0;
        });
      }

      let fromZoneIds: number[] = this.detailDiscountForm.get('fromZoneIds').value;
      if (subZones && subZones.length && fromZoneIds && fromZoneIds.length > 0) {
        fromZoneIds = fromZoneIds.filter(fromZoneId => {
          return subZones.findIndex(x => x.id === fromZoneId) !== -1;
        });
      } else
        fromZoneIds = [];

      this.detailDiscountForm.patchValue({ fromZoneIds: fromZoneIds }, { emitEvent: false });

      if (!this.detailLoading$.value && subZones && subZones.length === 1)
        this.detailDiscountForm.patchValue({ fromZoneIds: [subZones[0].id] }, { emitEvent: false });

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

  refreshToZones$ = new Subject<string>()
  toZonesForPipe$ = this.refreshToZones$.pipe(
    filter(() => !this.detailLoading$.value || this.manualChangesTriggering),
    map(() => {
      const pipelineId: number = this.detailForm.get('pipelineId').value;
      let subZones: Zone[] = [];

      if (pipelineId) {
        subZones = this.localRequiredData.zones.filter(zone => {
          return zone.pipelineId === pipelineId || zone.pipelineId === 0;
        });
      }

      let toZoneIds: number[] = this.detailDiscountForm.get('toZoneIds').value;
      if (subZones && subZones.length && toZoneIds && toZoneIds.length > 0) {
        toZoneIds = toZoneIds.filter(toZoneId => {
          return subZones.findIndex(x => x.id === toZoneId) !== -1;
        });
      } else
        toZoneIds = [];

      this.detailDiscountForm.patchValue({ toZoneIds: toZoneIds }, { emitEvent: false });

      if (!this.detailLoading$.value && subZones && subZones.length === 1)
        this.detailDiscountForm.patchValue({ toZoneIds: [subZones[0].id] }, { emitEvent: false });

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

  refreshFromMeters$ = new Subject<string>()
  metersForFromZones$ = this.refreshFromMeters$.pipe(
    filter(() => !this.detailLoading$.value || this.manualChangesTriggering),
    map(() => {
      const pipelineId: number = this.detailForm.get('pipelineId').value;
      const fromZoneIds: number[] = this.detailDiscountForm.get('fromZoneIds').value;
      const productId = Number(this.detailForm.get('productId').value);
      let filteredMeters: Meter[] = [];
      let subMeters: Meter[] = [];

      filteredMeters = this.localRequiredData.meters.filter(meter => meter.productId == productId);

      if (pipelineId && fromZoneIds && fromZoneIds.length > 0) {
        const hasAllFromZone = fromZoneIds.findIndex(zoneId => zoneId === 0) !== -1;
        subMeters = filteredMeters.filter(meter => {
          return fromZoneIds.findIndex(zoneId => (hasAllFromZone && pipelineId === meter.pipeId) || zoneId === meter.zoneId || meter.zoneId === 0) !== -1;
        });
      }

      let fromMeterIds: number[] = this.detailDiscountForm.get('fromMeterIds').value;
      if (subMeters && subMeters.length > 0 && fromMeterIds && fromMeterIds.length > 0) {
        fromMeterIds = fromMeterIds.filter(fromMeterId => {
          return subMeters.findIndex(x => x.meterId === fromMeterId) !== -1;
        });
      } else
        fromMeterIds = [];

      this.detailDiscountForm.patchValue({ fromMeterIds: fromMeterIds }, { emitEvent: false });

      if (!this.detailLoading$.value && subMeters && subMeters.length === 1)
        this.detailDiscountForm.patchValue({ fromMeterIds: [subMeters[0].meterId] }, { emitEvent: false });

      return subMeters;
    }),
    shareReplay(1)
  )

  refreshToMeters$ = new Subject<string>()
  metersForToZones$ = this.refreshToMeters$.pipe(
    filter(() => !this.detailLoading$.value || this.manualChangesTriggering),
    map(() => {
      const pipelineId: number = this.detailForm.get('pipelineId').value;
      const toZoneIds: number[] = this.detailDiscountForm.get('toZoneIds').value;
      const productId = Number(this.detailForm.get('productId').value);
      let filteredMeters: Meter[] = [];
      let subMeters: Meter[] = [];

      filteredMeters = this.localRequiredData.meters.filter(meter => meter.productId == productId);

      if (pipelineId && toZoneIds && toZoneIds.length > 0) {
        const hasAllToZone = toZoneIds.findIndex(zoneId => zoneId === 0) !== -1;
        subMeters = filteredMeters.filter(meter => {
          return toZoneIds.findIndex(zoneId => (hasAllToZone && pipelineId === meter.pipeId) || zoneId === meter.zoneId || meter.zoneId === 0) !== -1;
        });
      }

      let toMeterIds: number[] = this.detailDiscountForm.get('toMeterIds').value;
      if (subMeters && subMeters.length > 0 && toMeterIds && toMeterIds.length > 0) {
        toMeterIds = toMeterIds.filter(toMeterId => {
          return subMeters.findIndex(x => x.meterId === toMeterId) !== -1;
        });
      } else
        toMeterIds = [];

      this.detailDiscountForm.patchValue({ toMeterIds: toMeterIds }, { emitEvent: false });

      if (!this.detailLoading$.value && subMeters && subMeters.length === 1)
        this.detailDiscountForm.patchValue({ toMeterIds: [subMeters[0].meterId] }, { emitEvent: false });

      return subMeters;
    }),
    shareReplay(1)
  )

  fromZonesForPipeWithoutAllItem$ = this.fromZonesForPipe$.pipe(map(zones => zones.filter(x => x.id !== 0)))
  toZonesForPipeWithoutAllItem$ = this.toZonesForPipe$.pipe(map(zones => zones.filter(x => x.id !== 0)))

  manualTriggersCompleting$ = zip(
    this.fromZonesForPipe$,
    this.toZonesForPipe$,
    this.metersForFromZones$,
    this.metersForToZones$,
    this.fromZonesForPipeWithoutAllItem$,
    this.toZonesForPipeWithoutAllItem$
  ).pipe(
    filter(() => this.manualChangesTriggering),
    tap(() => {
      this.manualChangesTriggering = false;
      this.detailForm.enable();
      this.updateFormControlStates(null);
      this.detailLoading$.next(false);
      util.focusInputTarget();
    })
  )

  downloadDocResult$ = this.downloadDoc$.pipe(
    filter(([docItem,]) => {
      return docItem && docItem.fileNameOnDisk !== null;
    }),
    switchMap(([docItem, rateDocType]) => {
      return this.service.downloadDoc(docItem.fileNameOriginal, docItem.fileNameOnDisk, rateDocType);
    }),
    tap(res => {
      util.openOrSaveFile(res.fileBlob, res.fileName);
    }),
    catchError(err => {
      return util.handleError(err, this.messageService);
    }), retry(10)
  )

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

  openRateDetail(item: DetailRate): void {
    this.tooltipRateGrid.hide();
    this.tooltipDiscountGrid.hide();
    util.saveGridScrollPos(this.kendoRateGridEls, this.rateGridScrollPosition);
    this.detailForm.markAllAsTouched();
    if (this.detailForm.valid) {
      this.detailLoading$.next(true);
      setTimeout(() => {
        this.detailRateForm.reset();
        this.detailRateOpened$.next(true);
        if (item === null)
          item = this.detailRateInitialValues;
        item = this.updateMultiRateData(item, item.rateTypeId);
        this.detailRateForm.setValue(item);
        this.detailLoading$.next(false);
        util.focusInputTarget();
      }, 100);
    } else
      this.notify.error("validation failed");
  }

  updateMultiRateData(item: DetailRate, newRateTypeId: number): DetailRate {
    if (newRateTypeId !== 4 && (!item.multiRateData || item.multiRateData.length === 0)) //4 == annual adjustment charge
      item.multiRateData = [this.getNewRateDataItem()];

    this.detailRateDataFormArray = this.detailRateForm.get('multiRateData') as FormArray;
    this.detailRateDataFormArray.clear();

    (item.multiRateData as DetailRateData[]).forEach(rateData => {
      this.detailRateDataFormArray.push(this.getNewRateDataFormGroup(rateData));
    });

    return item;
  }

  getNewRateDataItem() {
    const rateData: DetailRateData = {
      orderId: null,
      startMonth: 1,
      endMonth: 12,
      fromZoneId: null,
      toZoneId: null,
      rate: null,
    }

    return rateData;
  }

  getNewRateDataFormGroup(rateData: DetailRateData) {
    const fb = this.fb;
    const fg: util.FormModel<DetailRateData> = fb.group({
      orderId: fb.ctr(rateData ? rateData.orderId : null),
      startMonth: fb.ctr(rateData ? rateData.startMonth : 1, Validators.required),
      endMonth: fb.ctr(rateData ? rateData.endMonth : 12, Validators.required),
      fromZoneId: fb.ctr(rateData ? rateData.fromZoneId : null, Validators.required),
      toZoneId: fb.ctr(rateData ? rateData.toZoneId : null, Validators.required),
      rate: fb.ctr(rateData ? rateData.rate : null, Validators.required),
    });

    return fg;
  }

  openDiscountDetail(item: DetailDiscount): void {
    this.tooltipRateGrid.hide();
    this.tooltipDiscountGrid.hide();
    this.detailDiscountForm.reset();
    this.detailForm.markAllAsTouched();
    if (this.detailForm.valid) {
      if (item === null)
        this.detailDiscountForm.setValue(this.detailDiscountInitialValues);
      else
        this.detailDiscountForm.setValue(item);
      this.detailDiscountOpened$.next(true);
      util.focusInputTarget();
    } else
      this.notify.error("validation failed");
  }

  openPipeSelect(): void {
    this.refreshPipeForProduct$.next(null);
    this.pipeSelectOpened$.next(true);
    this.pipeSelectForm.reset();
    util.focusInputTarget();
  }

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

  onDetailRateClosing() {
    util.onDetailChanging(this.detailRateForm, this.dialogService, this.closeDetailRate, this.setRate, { confirmType: 'set' });
  }

  onDetailDiscountClosing() {
    util.onDetailChanging(this.detailDiscountForm, this.dialogService, this.closeDetailDiscount, this.setDiscount, { confirmType: 'set' });
  }

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

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

  closeDetailRate = () => {
    this.detailRateOpened$.next(false);
    util.goToSavedGridScrollPos(this.kendoRateGridEls, this.rateGridScrollPosition);
  }

  closeDetailDiscount = () => {
    this.detailDiscountOpened$.next(false);
  }

  closePipeSelect() {
    this.pipeSelectOpened$.next(false);
  }

  detailFinishedLoading(): void {
    this.manualChangesTriggering = true;
    this.refreshFromZones$.next(null);
    this.refreshToZones$.next(null);
  }

  add(): void {
    this.pipeSelectForm.markAllAsTouched();
    if (this.pipeSelectForm.valid) {
      this.pipeSelectOpened$.next(false);
      this.openDetail(0);
    }
    else
      this.notify.error("validation failed");
  }

  addRate(): void {
    this.openRateDetail(null);
  }

  addDiscount(): void {
    this.openDiscountDetail(null);
  }

  edit(item: Item): void {
    this.openDetail(item.Id);
  }

  editRate(item: DetailRate): void {
    this.openRateDetail(item);
  }

  editDiscount(item: DetailDiscount): void {
    this.openDiscountDetail(item);
  }

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

  setRate = (saveType: util.SaveType) => {
    this.detailRateForm.markAllAsTouched();
    if (this.detailRateForm.valid) {
      const detailValue: Detail = new Detail(this.detailForm.value);
      const newDetailRate: DetailRate = this.detailRateForm.value as DetailRate;
      const isAddNewId = newDetailRate.id === 0;
      if (saveType === util.SaveType.New || isAddNewId) {
        newDetailRate.id = util.getRandomInt();
        //deep clone newDetailRate.documents so that the original array is not modified when adding/remove docs
        newDetailRate.documents = structuredClone(newDetailRate.documents);
        detailValue.rates.push(newDetailRate);
      } else {
        const detailRateIndex = detailValue.rates.findIndex(x => x.id === newDetailRate.id);
        detailValue.rates[detailRateIndex] = newDetailRate;
      }
      detailValue.updateStrings(this.localRequiredData);
      this.detailForm.setValue(detailValue);
      this.setReadOnlyNames();
      this.detailRateOpened$.next(false);
      this.rateDataStateChange(this.rateState);
      this.detailForm.markAsDirty();
      this.myRateSelection = [newDetailRate.id];
    }
    else
      this.notify.error("validation failed");
  }

  setDiscount = (saveType: util.SaveType) => {
    this.detailDiscountForm.markAllAsTouched();
    if (this.detailDiscountForm.valid) {
      const detailValue: Detail = new Detail(this.detailForm.value);
      const newDetailDiscount: DetailDiscount = this.detailDiscountForm.value as DetailDiscount;
      const isAddNewId = newDetailDiscount.id === 0;
      if (saveType === util.SaveType.New || isAddNewId) {
        newDetailDiscount.id = util.getRandomInt();
        detailValue.discounts.push(newDetailDiscount);
      } else {
        const detailDiscountIndex = detailValue.discounts.findIndex(x => x.id === newDetailDiscount.id);
        detailValue.discounts[detailDiscountIndex] = newDetailDiscount;
      }
      detailValue.updateStrings(this.localRequiredData);
      this.detailForm.setValue(detailValue);
      this.setReadOnlyNames();
      this.detailDiscountOpened$.next(false);
      this.discountDataStateChange(this.discountState);
      this.detailForm.markAsDirty();
      this.myDiscountSelection = [newDetailDiscount.id];
    }
    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);
    });
  }

  removeRate(): void {
    const rateId = this.detailRateForm.get('id').value;
    const detailValue: Detail = new Detail(this.detailForm.value);
    const indexToRemove = detailValue.rates.findIndex(x => x.id === rateId);
    detailValue.rates.splice(indexToRemove, 1);
    detailValue.updateStrings(this.localRequiredData);
    this.detailForm.setValue(detailValue);
    this.setReadOnlyNames();
    this.detailRateOpened$.next(false);
    this.rateDataStateChange(this.rateState);
  }

  removeDiscount(): void {
    const discountId = this.detailDiscountForm.get('id').value;
    const detailValue: Detail = new Detail(this.detailForm.value);
    const indexToRemove = detailValue.discounts.findIndex(x => x.id === discountId);
    detailValue.discounts.splice(indexToRemove, 1);
    detailValue.updateStrings(this.localRequiredData);
    this.detailForm.setValue(detailValue);
    this.setReadOnlyNames();
    this.detailDiscountOpened$.next(false);
    this.discountDataStateChange(this.discountState);
  }

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

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

  rateDataStateChange(state: State): void {
    this.rateState = state;
    this.rateGridData = process(this.detailForm.get('rates').value, state);
  }

  discountDataStateChange(state: State): void {
    this.discountState = state;
    this.discountGridData = process(this.detailForm.get('discounts').value, state);
  }

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

  updateFormControlStates(newRateTypeId: number) {
    if (newRateTypeId === null)
      newRateTypeId = this.detailRateForm.get('rateTypeId')?.value;

    if (newRateTypeId === 4) //annual charge adjustment
      this.detailRateDataFormArray.clear();
    else {
      this.detailRateForm.patchValue({ singleRate: null });
      this.updateMultiRateData(this.detailRateForm.value as DetailRate, newRateTypeId);
    }
  }

  docRateUploadSuccess(value: SuccessEvent) {
    const newdocitems: util.DocItem[] = value.response.body;
    let docs: util.DocItem[] = this.detailRateForm.get('documents').value;
    if (docs)
      docs = docs.concat(newdocitems);
    else
      docs = newdocitems;
    this.detailRateForm.patchValue({ documents: docs });
    this.detailRateForm.markAsDirty();
  }

  docDiscountUploadSuccess(value: SuccessEvent) {
    const newdocitems: util.DocItem[] = value.response.body;
    let docs: util.DocItem[] = this.detailDiscountForm.get('documents').value;
    if (docs)
      docs = docs.concat(newdocitems);
    else
      docs = newdocitems;
    this.detailDiscountForm.patchValue({ documents: docs });
    this.detailDiscountForm.markAsDirty();
  }

  docComplete() {
    if (this.docRateUploadElem)
      this.docRateUploadElem.clearFiles();

    if (this.docDiscountUploadElem)
      this.docDiscountUploadElem.clearFiles();
  }

  docUploadError(value: ErrorEvent) {
    util.handleError(value.response, this.messageService);
  }

  downloadRateDoc(doc: util.DocItem) {
    this.downloadDoc$.next([doc, 1]);
  }

  downloadDiscountDoc(doc: util.DocItem) {
    this.downloadDoc$.next([doc, 2]);
  }

  removeRateDoc(doc: util.DocItem) {
    const docs: util.DocItem[] = this.detailRateForm.get('documents').getRawValue();
    const indexToRemove = docs.findIndex(x => x.fileNameOnDisk === doc.fileNameOnDisk);
    docs.splice(indexToRemove, 1);
    this.detailRateForm.patchValue({ documents: docs });
    this.detailRateForm.markAsDirty();
  }

  removeDiscountDoc(doc: util.DocItem) {
    const docs: util.DocItem[] = this.detailDiscountForm.get('documents').getRawValue();
    const indexToRemove = docs.findIndex(x => x.fileNameOnDisk === doc.fileNameOnDisk);
    docs.splice(indexToRemove, 1);
    this.detailDiscountForm.patchValue({ documents: docs });
    this.detailDiscountForm.markAsDirty();
  }

  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  uploadProgress(event: UploadProgressEvent) {
    this.ref.detectChanges();
  }

  getScheduleName() {
    const name: string = this.detailForm.get('scheduleName').getRawValue();
    return name;
  }

  getPipelineName() {
    let name: string = null;
    const pipeId = this.detailForm.get('pipelineId').value;
    if (pipeId && typeof pipeId === 'number')
      name = this.localRequiredData.pipelines.find(x => x.pipeId === pipeId).pipeName;
    return name;
  }

  getContractTypeName() {
    let name: string = null;
    const typeId = this.detailForm.get('contractTypeId').value;
    if (typeId && typeof typeId === 'number')
      name = this.localRequiredData.contractTypes.find(x => x.id === typeId).name;
    return name;
  }

  getProductName() {
    let name: string = null;
    const typeId = this.detailForm.get('productId').value;
    if (typeId && typeof typeId === 'number')
      name = this.localRequiredData.products.find(x => x.id === typeId).name;
    return name;
  }

  addDetailRateData() {
    this.detailRateDataFormArray.push(this.getNewRateDataFormGroup(null));
    setTimeout(() => {
      this.detailRateElems.last.focus();
    });
  }

  removeDetailRateData(idx: number) {
    this.detailRateDataFormArray.removeAt(idx);
  }

  setReadOnlyNames() {
    this.productName = this.getProductName();
    this.pipeName = this.getPipelineName();
    this.scheduleName = this.getScheduleName();
    this.contractTypeName = this.getContractTypeName();
  }

  //for ScheduleDetails and DetailRates and DetailDiscounts
  filterPipelines$ = new BehaviorSubject<string>(null)
  pipelines$ = this.filterPipelines$.pipe(util.filterSpecials(this.gridLoading$, this.pipeForProduct$, null, 'pipeName'));

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

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

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

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

  filterFromZones$ = new BehaviorSubject<string>(null)
  fromZones$ = this.filterFromZones$.pipe(util.filterSpecials(this.detailLoading$, this.fromZonesForPipe$, null, 'name'));

  filterToZones$ = new BehaviorSubject<string>(null)
  toZones$ = this.filterToZones$.pipe(util.filterSpecials(this.detailLoading$, this.toZonesForPipe$, null, 'name'));

  filterFromMeters$ = new BehaviorSubject<string>(null)
  fromMeters$ = this.filterFromMeters$.pipe(util.filterSpecials(this.detailLoading$, this.metersForFromZones$, null, 'meterName'));

  filterToMeters$ = new BehaviorSubject<string>(null)
  toMeters$ = this.filterToMeters$.pipe(util.filterSpecials(this.detailLoading$, this.metersForToZones$, null, 'meterName'));

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

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

  filterFromZonesWithoutAllItem$ = new BehaviorSubject<string>(null)
  fromZonesWithoutAllItem$ = this.filterFromZonesWithoutAllItem$.pipe(util.filterSpecials(this.detailLoading$, this.fromZonesForPipeWithoutAllItem$, null, 'name'));

  filterToZonesWithoutAllItem$ = new BehaviorSubject<string>(null)
  toZonesWithoutAllItem$ = this.filterToZonesWithoutAllItem$.pipe(util.filterSpecials(this.detailLoading$, this.toZonesForPipeWithoutAllItem$, null, 'name'));

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