import { Component, ChangeDetectionStrategy, ChangeDetectorRef, ViewEncapsulation, Output, EventEmitter, Input, OnInit, ViewChild, ElementRef, AfterContentInit, model } from '@angular/core';
import { tap, map, catchError, switchMap, shareReplay, filter, retry, take } from 'rxjs/operators';
import { of, BehaviorSubject, Subject, Observable } from 'rxjs';
import { MessageService } from '../../_shared/services/message.service';
import { FormArray, FormGroup, Validators } from '@angular/forms';
import { DialogService, DialogSettings } from '@progress/kendo-angular-dialog';
import { NotifyService } from '../../_shared/services/notify.service';
import { SosTransfersService } from './sos-transfers.service';
import { DisplayInfo, SosSettingItem } from '../sos-settings/sos-settings.service';
import { CustomFormBuilder } from '../../_shared/services/custom-form-builder.service';
import * as models from '../models';
import * as util from '../../_shared/utils/util';
import { RequiredData, SosItem, SosPipeContractInfo, SosTransferLoadOptions } from '../models';
import dayjs from 'dayjs';
import { DatePickerComponent } from '@progress/kendo-angular-dateinputs';
import { AdjustType, SosHelper } from '../sosHelper';
import { ContextMenuSelectEvent } from '@progress/kendo-angular-menu';
import { AddDealParams } from '../sos-deal/sos-deal.service';
import { ActivatedRoute, Params } from '@angular/router';
import { DatePipe, Location, NgTemplateOutlet } from '@angular/common'; // Import Location service
import { FAST_KENDO_COMMON, FAST_PAGE_COMMON } from '../../app.config';
import { KFormatNumPipe } from '../../_shared/pipes/k-format-num.pipe';
import { SosElemComboComponent } from '../controls/sos-elem-combo';
import { SosElemNumericInputComponent } from '../controls/sos-elem-numeric-input';
import { SosElemTextComponent } from '../controls/sos-elem-text';
import { SosRowMainColorsPipe } from '../pipes/sos-row-main-colors.pipe';
import { SosDealComponent } from '../sos-deal/sos-deal.component';
import { SosNoteComponent } from '../sos-note/sos-note.component';
import { SosSaveComponent } from '../sos-save/sos-save.component';

export interface PathVars {
  isPathStart: boolean;
  isPathEnd: boolean;
}

@Component({
  selector: 'app-sos-gas-transfers[nomDate][pipeId][pointId][requiredData][settings]',
  templateUrl: './sos-transfers.component.html',
  styleUrls: ['./sos-transfers.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None,
  imports: [FAST_PAGE_COMMON, FAST_KENDO_COMMON, NgTemplateOutlet, KFormatNumPipe, DatePipe, SosElemTextComponent, SosElemComboComponent, SosElemNumericInputComponent, SosRowMainColorsPipe, SosSaveComponent, SosNoteComponent, SosDealComponent]
})
export class SosTransfersComponent implements OnInit, AfterContentInit {
  @ViewChild('frozenColValues') frozenColValuesDiv: ElementRef<HTMLDivElement>;
  @ViewChild('normalColValues') normalColValuesDiv: ElementRef<HTMLDivElement>;
  @ViewChild('normalColsTopSection') normalColsTopSectionDiv: ElementRef<HTMLDivElement>;
  @Input() nomDate: Date;
  @Input() pipeId: number;
  @Input() pointId: number;
  @Input() requiredData: models.RequiredData;
  @Input() settings: SosSettingItem;
  sosItem = model<SosItem>(null);
  @Output() closed = new EventEmitter<boolean>()
  @ViewChild('nomDateElem') nomDatePicker: DatePickerComponent;

  util = util;
  icons = util.icons;
  models = models;
  hasModifyPermission = false;
  showMainTooltip = SosHelper.showMainTooltip;
  allContracts: SosPipeContractInfo[];

  gridLoading$ = new BehaviorSubject<boolean>(true);
  frozenColCount: number;
  fontSizeStr: string;
  displays: { [key: string]: DisplayInfo };
  displayInfos: DisplayInfo[];
  doc = document;
  transferSetForm: util.FormModel<models.SosTransferSet>;
  itemFormArray: FormArray;
  itemForms: FormGroup[];
  pathTitleMarginTopStr: string;
  loadForm: util.FormModel<SosTransferLoadOptions>;
  isFirstLoad = true;
  sosTipClass: string = '';
  selectedItemForm: FormGroup;
  addDealParams: AddDealParams = null;
  areCustomContextMenusEnabled: boolean = true;

  result$: Observable<models.SosTransferSet>;
  refreshTransferSet$ = new Subject<boolean>();
  pipeContractIdUnsubscribe$ = new Subject();
  ptrContractIdUnsubscribe$ = new Subject();
  ptrDeliveryMeterIdUnsubscribe$ = new Subject();
  ptrPercentUnsubscribe$ = new Subject();
  deliveryVolUnsubscribe$ = new Subject();
  nomDateChanged$: Observable<Date>;
  saveNomsOpened$ = new BehaviorSubject<boolean>(false);
  noteOpened$ = new BehaviorSubject<boolean>(false);
  setHoveredItem$ = new BehaviorSubject<FormGroup>(null);
  hoveredItemResult$: Observable<string>;
  deleteTransfersResult$: Observable<object>
  deleteTransfers$ = new Subject<string>();
  addDealOpened$ = new BehaviorSubject<boolean>(false);

  requiredData$: Observable<RequiredData>;
  dealId: number;
  transferDealId: number;
  transferDealDate: string;
  tneMeterId: number;
  actualItem: models.ActualItem = {
    actualTypeId: undefined,
    supplyNomId: undefined,
    marketNomId: undefined,
    lastTransferId: undefined,
    saveDate: undefined,
    tneMeterId: undefined,
    nomDate: undefined,
    isLinked: undefined,
  };

  constructor(private messageService: MessageService, private service: SosTransfersService, private fb: CustomFormBuilder, private ref: ChangeDetectorRef, private dialogService: DialogService, private notify: NotifyService, private activatedRoute: ActivatedRoute, private location: Location) {
    //console.log(this.sosItem());
  }

  //use ngAfterContentInit instead of ngAfterViewInit because ngAfterViewInit causes an "Expression has changed after it was checked" error
  ngAfterContentInit(): void {
    // console.log('SosTransfersComponent.ngAfterContentInit()');
    // console.log(this.sosItem());

    this.activatedRoute.queryParams.pipe(take(1)).subscribe(queryParams => {

      this.fillActualItemFromQueryParams(queryParams);
      console.log(`this.actualItem: ${this.actualItem}`);

      this.fillDealIdFromQueryParams(queryParams);
      console.log(`this.dealId: ${this.dealId}`);

      this.fillTransferDealIdFromQueryParams(queryParams);
      console.log(`this.transferDealId: ${this.transferDealId}`);

      this.fillTransferDealDateFromQueryParams(queryParams);
      console.log(`this.transferDealDate: ${this.transferDealDate}`);

      // log the full URL to console
      const fullUrl = window.location.origin + '/#' + this.location.path();
      console.log('Full URL:', fullUrl);

      if (this.isCalledExternally()) {
        this.areCustomContextMenusEnabled = false;

        let requiredData$: Observable<models.SosTransferData>;
        if (this.isCalledFromDeal()) {
          requiredData$ = this.service.getRequiredData(this.dealId);
        } else if (this.isCalledFromActualization()) {
          requiredData$ = this.service.getRequiredDataForTransferDeal(this.transferDealId, this.transferDealDate);
        } else if (this.isCalledFromActualTneMeter()) {
          requiredData$ = this.service.getRequiredDataForActualTneMeter(this.actualItem);
        }

        requiredData$.pipe(take(1)).subscribe(sosTransferData => {
          // sosTransferData.nomDate is actually a string for some reason,
          // so we have to convert it to a date object
          this.nomDate = dayjs(sosTransferData.nomDate).toDate();
          this.loadForm.patchValue({ nomDate: this.nomDate });

          this.pipeId = sosTransferData.pipelineId;
          this.pointId = sosTransferData.pointId;
          this.requiredData = sosTransferData.requiredData;
          this.settings = sosTransferData.sosSettingItem;
          this.tneMeterId = sosTransferData.tneMeterId;

          const sosItem = sosTransferData.sosDataSet.items.find(item => item.dealId === Number(this.dealId));
          const localSosItemIsNull = sosItem === null || sosItem === undefined;
          const thisSosItemIsNull = this.sosItem() === null || this.sosItem() === undefined;
          if (!localSosItemIsNull && thisSosItemIsNull)
            this.sosItem.set(sosItem);

          this.refreshTransferSet$.next(true);
        });
      }
      else {
        // for some reason refreshTransferSet$ won't trigger properly here without a delay.
        // even a 1ms delay seems to work, but I used 10ms, just to be safe.
        setTimeout(() => this.refreshTransferSet$.next(true), 10);
      }

    });
  }

  /// <summary>
  /// Determines if this screen was called directly from the deal screen
  /// (as opposed to being called from the main SOS screen).
  /// Only works correctly after ngOnInit() and ngAfterContentInit() are called.
  /// </summary>
  isCalledFromDeal(): boolean {
    const value = this.dealId !== null && this.dealId !== undefined && !isNaN(this.dealId);
    return value;
  }

  /// <summary>
  /// Determines if this screen was called directly from the Actualization screen - Deal column
  /// (as opposed to being called from the main SOS screen).
  /// Only works correctly after ngOnInit() and ngAfterContentInit() are called.
  /// </summary>
  isCalledFromActualization(): boolean {
    const value = this.transferDealId !== null && this.transferDealId !== undefined && !isNaN(this.transferDealId);
    return value;
  }

  /// <summary>
  /// Determines if this screen was called directly from the Actualization screen - T&E Meter column
  /// (as opposed to being called from the main SOS screen).
  /// Only works correctly after ngOnInit() and ngAfterContentInit() are called.
  /// </summary>
  isCalledFromActualTneMeter(): boolean {
    const value = this.actualItem != null
      && this.actualItem.actualTypeId != null
      && this.actualItem.supplyNomId != null
      && this.actualItem.marketNomId != null
      && this.actualItem.saveDate != null
      && this.actualItem.nomDate != null
    return value;
  }

  /// <summary>
  /// Determines if this screen was called from outside the main SOS screen.
  /// Only works correctly after ngOnInit() and ngAfterContentInit() are called.
  /// </summary>
  isCalledExternally(): boolean {
    return this.isCalledFromDeal() || this.isCalledFromActualization() || this.isCalledFromActualTneMeter();
  }

  setResult(): void {
    this.result$ = this.refreshTransferSet$.pipe(
      filter(force => {
        this.loadForm.updateValueAndValidity({ emitEvent: false, onlySelf: true });
        const loadOps = this.loadForm.value as SosTransferLoadOptions;
        const hasNomDate = (loadOps.nomDate != null) || (this.nomDate != null);
        const isAlreadyLoaded = dayjs(loadOps.nomDate).isSame(this.nomDate, 'day');
        return hasNomDate && (!isAlreadyLoaded || this.isFirstLoad || force);
      }),
      tap(() => {
        this.gridLoading$.next(true);
        this.transferSetForm.disable();
      }),
      util.fastDebounce(500),
      switchMap(() => {
        const loadOps = this.loadForm.value as SosTransferLoadOptions;
        if (this.isCalledFromDeal())
          return this.service.getTransferSetForDeal(this.dealId);
        else if (this.isCalledFromActualization())
          return this.service.getTransferSetForTransferDeal(this.transferDealId, this.transferDealDate);
        else if (this.isCalledFromActualTneMeter())
          return this.service.getTransferSetForTneMeter(this.actualItem);
        else
          return this.service.getTransferSet(this.sosItem(), loadOps.nomDate);
      }),
      map(result => {
        // console.log('after getTransferSetForDeal/getTransferSet:');
        // console.log(this.sosItem());

        if (this.isCalledExternally()) {
          if (result?.sosItem !== null && this.sosItem() !== null) {
            this.sosItem().supplyCounterparty = result.sosItem.supplyCounterparty;
            this.sosItem().receiptMeter = result.sosItem.receiptMeter;
            this.sosItem().sourceTicket = result.sosItem.sourceTicket;
            this.sosItem().dealType = result.sosItem.dealType;
          }
        }

        this.isFirstLoad = false;
        const loadOps = this.loadForm.value as SosTransferLoadOptions;
        this.nomDate = loadOps.nomDate;
        this.allContracts = result.pipeContracts;
        SosHelper.adjustPercents(result.items, AdjustType.ForDisplay);
        this.updateSettings(this.settings);
        this.updateRowNums(result.items);
        this.setTransferSetValue(result);
        return result;
      }),
      tap(() => {
        this.transferSetForm.markAsPristine();
        this.transferSetForm.enable();
        this.gridLoading$.next(false);

        if ((this.isCalledFromDeal() || this.isCalledFromActualTneMeter()) && typeof this.tneMeterId === 'number') {
          const item = this.getTneItem();
          this.setSelectedItem(item);
        } else {
          this.setSelectedItem(null);
        }
      }),
      shareReplay(1),
      catchError(err => {
        this.transferSetForm.enable();
        this.gridLoading$.next(false);
        return util.handleError(err, this.messageService);
      }), retry(10)
    );
  }

  getTneItem(): FormGroup {
    const value = this.itemForms.find(item => item.get('receiptMeterId').value === this.tneMeterId);
    return value || null;  // return null if not found (undefined)
  }

  ngOnInit(): void {
    // console.log('SosTransfersComponent.ngOnInit()');
    // console.log(this.sosItem());

    if (this.sosItem() === null || this.sosItem() === undefined) {
      const placeholderItem = new SosItem();
      // placeholderItem.supplyCounterparty = " ";
      // placeholderItem.receiptMeter = " ";
      // placeholderItem.sourceTicket = " ";
      // placeholderItem.dealType = " ";
      this.sosItem.set(placeholderItem);
    }

    this.loadForm = this.getLoadForm();
    this.transferSetForm = this.getTransferSetForm();

    this.setResult();

    this.nomDateChanged$ = this.loadForm.get('nomDate').valueChanges.pipe(
      filter(() => {
        return this.loadForm.get('nomDate').valid;
      }),
      tap(() => {
        const cancelFunc = () => {
          this.loadForm.patchValue({ nomDate: this.nomDate }, { emitEvent: false });
        };

        const dontSaveFunc = () => {
          this.refreshTransferSet$.next(false);
        };

        if (this.isGridDirty())
          util.onDetailChanging(this.itemForms, this.dialogService, dontSaveFunc, this.openSaveNoms, { fnCancelAction: cancelFunc, skipPristine: true });
        else
          dontSaveFunc();
      }),
      shareReplay(1),
      catchError(err => {
        return util.handleError(err, this.messageService);
      }), retry(1)
    );

    this.hoveredItemResult$ = this.setHoveredItem$.pipe(
      switchMap(form => {
        SosHelper.setHoveredItem(this.itemForms, form);
        return of(null);
      })
    );

    this.deleteTransfersResult$ = this.deleteTransfers$.pipe(
      tap(() => {
        this.gridLoading$.next(true);
        this.transferSetForm.disable();
        this.loadForm.disable();
      }),
      switchMap(pathGuid => {
        //get all items in the same path
        const itemsWithSamePathGuid: models.SosTransferItem[] = this.itemForms.filter(x => x.getRawValue().pathGuid == pathGuid).map(x => x.getRawValue());
        //get the items in the path that have a supply or market transfer deal
        const supplyTransferDealIds = itemsWithSamePathGuid.filter(x => x.supplyTransferDealId).map(x => x.supplyTransferDealId);
        const marketTransferDealIds = itemsWithSamePathGuid.filter(x => x.marketTransferDealId).map(x => x.marketTransferDealId);
        const transferDealIds = [...supplyTransferDealIds, ...marketTransferDealIds];
        const firstTransferDealId = transferDealIds[0];
        const loadOps = this.loadForm.value as SosTransferLoadOptions;
        return this.service.deleteTransfersInPath(this.sosItem(), loadOps.nomDate, firstTransferDealId);
      }),
      tap(() => {
        this.closed.emit(true);
        this.notify.success('transfers deleted');
        this.loadForm.enable();
      }),
      shareReplay(1),
      catchError(err => {
        this.loadForm.enable();
        this.transferSetForm.enable();
        this.gridLoading$.next(false);
        return util.handleError(err, this.messageService);
      })
    )

    this.setPathTitleMarginTop(0);
  }

  filterMeters(filterText: string, item: models.SosTransferItem) {
    const meters = [...this.requiredData.meters];
    const metersForPipe = item.receiptPipeId ? meters.filter(x => x.pipeId == item.receiptPipeId) : meters;
    item.metersForPipe = util.filterLocalObject(filterText, this.gridLoading$.value, metersForPipe, null, 'meterNameAndNum');
  }

  filterContracts(filterText: string, item: models.SosTransferItem) {
    const contracts = [...this.allContracts];
    const sourceDealProduct = this.sosItem()?.sourceDealProduct;
    const contractsForPipe = item.receiptPipeId
      ? contracts.filter(x => x.pipeId === item.receiptPipeId && x.productId === sourceDealProduct)
      : contracts;
    item.contractsForPipe = util.filterLocalObject(filterText, this.gridLoading$.value, contractsForPipe, null, 'contractName');
  }

  filterPtrContracts(filterText: string, item: models.SosTransferItem) {
    const contracts = [...this.allContracts];
    const sourceDealProduct = this.sosItem()?.sourceDealProduct;
    const contractsForPipe = item.receiptPipeId
      ? contracts.filter(x => x.pipeId === item.receiptPipeId && x.productId === sourceDealProduct && x.isPtrContract)
      : contracts;
    item.ptrContractsForPipe = util.filterLocalObject(filterText, this.gridLoading$.value, contractsForPipe, null, 'contractName');
  }

  getLoadForm() {
    const fb = this.fb;
    const fg: util.FormModel<models.SosTransferLoadOptions> = this.fb.group({
      nomDate: fb.ctr(this.nomDate, { validators: [Validators.required], updateOn: 'blur' })
    });

    return fg;
  }

  getTransferSetForm() {
    const fb = this.fb;
    const fg: util.FormModel<models.SosTransferSet> = fb.group({
      items: fb.arr([]),
      pipeContracts: fb.ctr(null), //not used in form
      sosItem: fb.ctr(null)
    });

    this.itemFormArray = fg.get('items') as FormArray;
    this.itemForms = this.itemFormArray.controls as FormGroup[];

    return fg;
  }

  getItemForm(sosItem: models.SosTransferItem) {
    const fb = this.fb;
    const fg: util.FormModel<models.SosTransferItem> = this.fb.group({
      rowNum: fb.ctr(sosItem ? sosItem.rowNum : -1),
      guid: fb.ctr(sosItem ? sosItem.guid : null, Validators.required),
      pathGuid: fb.ctr(sosItem ? sosItem.pathGuid : Validators.required),
      pathName: fb.ctr(sosItem ? sosItem.pathName : null),
      supplyTicket: fb.ctr(sosItem ? sosItem.supplyTicket : null),
      receiptPipe: fb.ctr(sosItem ? sosItem.receiptPipe : null),
      receiptMeter: fb.ctr(sosItem ? sosItem.receiptMeter : null),
      pipeContractId: fb.ctr(sosItem ? sosItem.pipeContractId : null),
      ptrContractId: fb.ctr(sosItem ? sosItem.ptrContractId : null),
      ptrDeliveryMeterId: fb.ctr(sosItem ? sosItem.ptrDeliveryMeterId : null),
      ptrPercent: fb.ctr(sosItem ? sosItem.ptrPercent : null),
      ptrAmount: fb.ctr(sosItem ? sosItem.ptrAmount : null),
      ptrFuelPercent: fb.ctr(sosItem ? sosItem.ptrFuelPercent : null),
      ptrFuelAmount: fb.ctr(sosItem ? sosItem.ptrFuelAmount : null),
      totalReceiptVol: fb.ctr(sosItem ? sosItem.totalReceiptVol : null),
      nomReceiptVol: fb.ctr(sosItem ? sosItem.nomReceiptVol : null),
      nomFuelPercent: fb.ctr(sosItem ? sosItem.nomFuelPercent : null),
      nomFuelAmount: fb.ctr(sosItem ? sosItem.nomFuelAmount : null),
      dealVolume: fb.ctr(sosItem ? sosItem.dealVolume : null),
      deliveryVol: fb.ctr(sosItem ? sosItem.deliveryVol : null, { updateOn: 'blur' }),
      deliveryPipe: fb.ctr(sosItem ? sosItem.deliveryPipe : null),
      deliveryMeter: fb.ctr(sosItem ? sosItem.deliveryMeter : null),
      transferNum: fb.ctr(sosItem ? sosItem.transferNum : null),
      modifiedProps: fb.ctr(sosItem ? sosItem.modifiedProps : null),
      supplyDealId: fb.ctr(sosItem ? sosItem.supplyDealId : null),
      receiptPipeId: fb.ctr(sosItem ? sosItem.receiptPipeId : null),
      receiptPointId: fb.ctr(sosItem ? sosItem.receiptPointId : null),
      receiptMeterId: fb.ctr(sosItem ? sosItem.receiptMeterId : null),
      supplyTransferDealId: fb.ctr(sosItem ? sosItem.supplyTransferDealId : null),
      marketDealId: fb.ctr(sosItem ? sosItem.marketDealId : null),
      deliveryPointId: fb.ctr(sosItem ? sosItem.deliveryPointId : null),
      deliveryMeterId: fb.ctr(sosItem ? sosItem.deliveryMeterId : null),
      marketTransferDealId: fb.ctr(sosItem ? sosItem.marketTransferDealId : null),
      activityNum: fb.ctr(sosItem ? sosItem.activityNum : null),
      isKeepWhole: fb.ctr(sosItem ? sosItem.isKeepWhole : false),
      supplyNotes: fb.ctr(sosItem ? sosItem.supplyNotes : null),
      supplySourceNotes: fb.ctr(sosItem ? sosItem.supplySourceNotes : null),
      nomNotes: fb.ctr(sosItem ? sosItem.nomNotes : null),
      marketTransferMeterMapId: fb.ctr(sosItem ? sosItem.marketTransferMeterMapId : null),
      isManualPtr: fb.ctr(sosItem ? sosItem.isManualPtr : false),
      pathLegNum: fb.ctr(sosItem ? sosItem.pathLegNum : null),
      metersForPipe: fb.ctr(sosItem ? sosItem.metersForPipe : []),
      contractsForPipe: fb.ctr(sosItem ? sosItem.contractsForPipe : []),
      ptrContractsForPipe: fb.ctr(sosItem ? sosItem.ptrContractsForPipe : []),
      isEnteredFromSos: fb.ctr(sosItem ? sosItem.isEnteredFromSos : false),
      isSelected: fb.ctr(sosItem ? sosItem.isSelected : false),
      isHovered: fb.ctr(sosItem ? sosItem.isHovered : false)
    });

    return fg;
  }

  setTransferSetValue(transferSet: models.SosTransferSet) {
    util.convertToDates(transferSet);
    this.setMetersForPipe(transferSet);
    this.setContractsForPipe(transferSet);
    this.refreshFormArrays(transferSet);
    this.transferSetForm.setValue(transferSet);
    this.rewatch();
  }

  setMetersForPipe(transferSet: models.SosTransferSet) {
    transferSet.items.forEach(item => {
      this.filterMeters(null, item);
    });
  }

  setContractsForPipe(transferSet: models.SosTransferSet) {
    transferSet.items.forEach(item => {
      this.filterContracts(null, item);
      this.filterPtrContracts(null, item);
    });
  }

  updateSettings(newSettings: SosSettingItem) {
    this.fontSizeStr = newSettings.fontSize + 'px';
    this.frozenColCount = newSettings.transferFrozenColumnCount;
    this.displayInfos = newSettings.transferDisplayInfos;
    this.sosTipClass = 'app-sosTip-' + newSettings.fontSize;

    this.displays = this.displayInfos.reduce((acc: { [key: string]: DisplayInfo }, x) => {
      acc[x.propName] = x;
      return acc;
    }, {});
  }

  updateRowNums(items: models.SosTransferItem[] = null) {
    let rowNum: number = 1;

    if (items === null) {
      this.itemForms.forEach((itemForm) => {
        itemForm.patchValue({ rowNum: rowNum }, { emitEvent: false });
        rowNum++;
      });
    }
    else {
      items.forEach((item) => {
        item.rowNum = rowNum;
        rowNum++;
      });
    }
  }

  refreshFormArrays(transferSet: models.SosTransferSet) {
    this.itemFormArray.clear();

    transferSet.items.forEach(item => {
      const itemForm = this.getItemForm(item);
      this.itemFormArray.push(itemForm);
    });
    this.itemForms = this.itemFormArray.controls as FormGroup[];
  }

  rewatch() {
    setTimeout(() => {
      util.watchForFormArrayChanges(this.itemFormArray, 'pipeContractId', this.pipeContractIdUnsubscribe$, this.propChanged, this.gridLoading$);
      util.watchForFormArrayChanges(this.itemFormArray, 'ptrContractId', this.ptrContractIdUnsubscribe$, this.propChanged, this.gridLoading$);
      util.watchForFormArrayChanges(this.itemFormArray, 'ptrDeliveryMeterId', this.ptrDeliveryMeterIdUnsubscribe$, this.propChanged, this.gridLoading$);
      util.watchForFormArrayChanges(this.itemFormArray, 'ptrPercent', this.ptrPercentUnsubscribe$, this.propChanged, this.gridLoading$);
      util.watchForFormArrayChanges(this.itemFormArray, 'deliveryVol', this.deliveryVolUnsubscribe$, this.deliveryVolChanged, this.gridLoading$);
    }, 0);
  }

  sendWindowCloseMessage(): void {
    // console.log("window.parent.postMessage('close', '*')");
    window.parent.postMessage('close', '*');
  }

  close(): void {
    if (this.isCalledExternally()) {
      this.sendWindowCloseMessage();
      this.closed.emit(false);
    } else {
      if (document.activeElement instanceof HTMLInputElement) {
        document.activeElement.blur();
        //call an empty setTimeout function for the blur to trigger a dirty form
        //this doesn't actually wait for the blur to happen, but it seems to be enough to work
        setTimeout(() => { }, 100);
      }

      const dontSaveFunc = () => {
        this.closed.emit(false);
      };

      if (this.isGridDirty())
        util.onDetailChanging(this.itemForms, this.dialogService, dontSaveFunc, this.openSaveNoms, { skipPristine: true });
      else
        dontSaveFunc();
    }
  }

  setPathTitleMarginTop(scrollTop: number) {
    this.pathTitleMarginTopStr = 20 - scrollTop + 'px';
  }

  getPathVars(itemIdx: number): PathVars {
    const previousPathName = this.itemForms[itemIdx - 1]?.get('pathName')?.value;
    const currentPathName = this.itemForms[itemIdx]?.get('pathName')?.value;
    const nextPathName = this.itemForms[itemIdx + 1]?.get('pathName')?.value;
    const isPathStart = currentPathName && currentPathName != previousPathName;
    const isPathEnd = currentPathName && currentPathName != nextPathName;
    return { isPathStart: isPathStart, isPathEnd: isPathEnd };
  }

  deliveryVolChanged = (change: util.FormArrayChange) => {
    if (this instanceof SosTransfersComponent) {
      const changedItem = change.form.getRawValue() as models.SosTransferItem;
      const pathItemForms = this.itemForms.filter(x => x.get('pathName').value == changedItem.pathName);

      //legNum is 1 based but pathItemForms array is 0 based
      //pathLegNum is equivalent to the array index of the next item, following the changed item
      const nextItemIndex = changedItem.pathLegNum;
      const prevItemIndex = changedItem.pathLegNum - 2;
      const lastItemIndex = pathItemForms.length - 1;
      this.recalculate(change.form);
      this.patchModifiedProps(change.form, 'deliveryVol');

      //Update vols from top to bottom
      for (let i = nextItemIndex; i < lastItemIndex; i++) {
        const itemForm = pathItemForms[i];
        const itemValue = itemForm.getRawValue() as models.SosTransferItem;
        SosHelper.adjustPercents([itemValue], AdjustType.ForSave);
        const sourceVol = pathItemForms[i - 1].get('deliveryVol').value as number;
        const deliveryVol = util.round(sourceVol / (1 + (1 / (1 - itemValue.nomFuelPercent) - 1) + (1 / (1 - itemValue.ptrPercent) - 1) + ((1 / (1 - itemValue.ptrPercent) - 1) / (1 - itemValue.ptrFuelPercent) - (1 / (1 - itemValue.ptrPercent) - 1))), 0);
        itemForm.patchValue({ deliveryVol: deliveryVol }, { emitEvent: false });
        this.recalculate(itemForm);
        this.patchModifiedProps(itemForm, 'deliveryVol');
      }

      //Update vols from bottom to top
      for (let i = prevItemIndex; i >= 0; i--) {
        const itemForm = pathItemForms[i];
        const sourceVol = pathItemForms[i + 1].get('totalReceiptVol').value as number;
        itemForm.patchValue({ deliveryVol: sourceVol }, { emitEvent: false });
        this.recalculate(itemForm);
        this.patchModifiedProps(itemForm, 'deliveryVol');
      }

      //Update deal vols
      for (let i = 1; i <= lastItemIndex; i++) {
        const itemForm = pathItemForms[i];
        const itemValue = itemForm.getRawValue() as models.SosTransferItem;
        const sourceVol = pathItemForms[i - 1].get('deliveryVol').value as number;
        //If the path has has any items with the loaded transfer deal id, then update the deal volume AKA "Volume to Transfer"
        if (itemValue.supplyTransferDealId === this.sosItem().transferDealId || itemValue.marketTransferDealId === this.sosItem().transferDealId)
          this.sosItem().dealVolume = pathItemForms[0].get('deliveryVol').value as number; //used to update "Volume to Transfer"
        itemForm.patchValue({ dealVolume: sourceVol }, { emitEvent: false });
      }
    }
  }

  recalculate(itemForm: FormGroup) {
    const itemValue = itemForm.getRawValue() as models.SosTransferItem;
    SosHelper.adjustPercents([itemValue], AdjustType.ForSave);
    const ptrAmount = util.round(itemValue.deliveryVol / (1 - itemValue.ptrPercent) - itemValue.deliveryVol, 0)
    const nomFuelAmount = util.round(itemValue.deliveryVol / (1 - itemValue.nomFuelPercent) - itemValue.deliveryVol, 0);
    const ptrFuelAmount = util.round(ptrAmount / (1 - itemValue.ptrFuelPercent) - ptrAmount, 0);
    const nomReceiptVol = itemValue.deliveryVol + nomFuelAmount;
    const ptrRecVol = ptrAmount + ptrFuelAmount;
    const totalReceiptVol = nomReceiptVol + ptrRecVol;
    itemForm.patchValue({ ptrAmount: ptrAmount, nomFuelAmount: nomFuelAmount, ptrFuelAmount: ptrFuelAmount, nomReceiptVol: nomReceiptVol, ptrRecVol: ptrRecVol, totalReceiptVol: totalReceiptVol }, { emitEvent: false });
  }

  propChanged = (change: util.FormArrayChange) => {
    if (this instanceof SosTransfersComponent)
      this.patchModifiedProps(change.form, change.formFieldName);
  }

  patchModifiedProps(form: FormGroup, propName: string) {
    const modifiedProps: string[] = form.get('modifiedProps').value;
    if (!modifiedProps.includes(propName))
      modifiedProps.push(propName);
    form.patchValue({ modifiedProps: modifiedProps }, { emitEvent: false });
  }

  prevDate() {
    if (this.loadForm.get('nomDate').valid) {
      const nomDate = dayjs(this.loadForm.value.nomDate as Date).add(-1, 'day').toDate()
      this.loadForm.patchValue({ nomDate: nomDate });
    }
  }

  nextDate() {
    if (this.loadForm.get('nomDate').valid) {
      const nomDate = dayjs(this.loadForm.value.nomDate as Date).add(1, 'day').toDate();
      this.loadForm.patchValue({ nomDate: nomDate });
    }
  }

  onNomDatePickerClose() {
    setTimeout(() => {
      this.nomDatePicker.blur();
    });
  }

  openSaveNoms = () => {
    if (this.isCalledFromDeal()) {
      // console.log(`openSaveNoms(): this.tneMeterId = ${this.tneMeterId}`);
      this.service.saveTneMeter(this.dealId, this.tneMeterId).pipe(take(1)).subscribe(() => {
        // console.log("saveTneMeter() success");
        this.notify.success('T&E deduction meter saved');
        this.close();
      });
    }
    else if (this.isCalledFromActualTneMeter()) {
      this.actualItem.tneMeterId = this.tneMeterId;
      this.service.saveActualTneMeter(this.actualItem).pipe(take(1)).subscribe(() => {
        this.notify.success('T&E deduction meter saved');
        this.close();
      });
    }
    else {
      this.loadForm.patchValue({ nomDate: this.nomDate }, { emitEvent: false });
      if (this.transferSetForm.dirty) {
        this.setSelectedItem(null);
        this.saveNomsOpened$.next(true);
      }
      else
        this.notify.warning('no changes detected');
    }
  }

  saveNomsClosed(areNomsSaved: boolean) {
    this.saveNomsOpened$.next(false);
    if (areNomsSaved) {
      this.closed.emit(true);
      this.notify.success('transfers saved');
    }
  }

  toItem(item: models.SosTransferItem): models.SosTransferItem {
    //if the item is null then create a new empty item
    //this is needed because we still want the values template to be rendered for width purposes when we have a null item
    if (item === null)
      item = {} as models.SosTransferItem;
    return item;
  }

  setSelectedItem(itemForm: FormGroup) {
    this.selectedItemForm = itemForm;
    this.tneMeterId = itemForm ? itemForm.get('receiptMeterId').value : null;
    SosHelper.setSelectedItem(this.itemForms, itemForm);
  }

  onContextMenuSelect(e: ContextMenuSelectEvent, item: models.SosTransferItem) {
    const contextMenuItem: models.SosContextItem = e.item;
    const itemForm = this.itemForms.find(x => x.value.guid === item.guid);
    this.selectedItemForm = itemForm;

    if (contextMenuItem.menuOption === models.SosContextMenuOptions.KeepWhole)
      this.toggleKeepWhole(itemForm);
    else if (contextMenuItem.menuOption === models.SosContextMenuOptions.EditNomNote)
      this.showNote();
    else if (contextMenuItem.menuOption === models.SosContextMenuOptions.DeleteTransfersInPath)
      this.DeleteTransfersInPath(itemForm.value.pathGuid);
  }

  toggleKeepWhole(itemForm: FormGroup) {
    const isKeepWhole: boolean = !itemForm.value.isKeepWhole;
    itemForm.controls['isKeepWhole'].patchValue(isKeepWhole);
    itemForm.controls['isKeepWhole'].markAsDirty();
    this.patchModifiedProps(itemForm, 'deliveryVol');
  }

  DeleteTransfersInPath(pathGuid: string) {
    const dontSaveFunc = () => {
      const deleteConfirmSettings: DialogSettings = {
        title: "Please confirm",
        content: "Are you sure you want to delete all the transfers in this path?",
        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.deleteTransfers$.next(pathGuid);
      });
    };

    if (this.isGridDirty())
      util.onDetailChanging(this.itemForms, this.dialogService, dontSaveFunc, this.openSaveNoms, { skipPristine: true });
    else
      dontSaveFunc();
  }

  isGridDirty(): boolean {
    const isDirty = this.itemForms.some(f => f.dirty) ? true : false;
    return isDirty;
  }

  syncScroll() {
    if (this.frozenColValuesDiv && this.normalColValuesDiv)
      this.frozenColValuesDiv.nativeElement.scrollTop = this.normalColValuesDiv.nativeElement.scrollTop;
    if (this.normalColsTopSectionDiv && this.normalColValuesDiv)
      this.normalColsTopSectionDiv.nativeElement.scrollLeft = this.normalColValuesDiv.nativeElement.scrollLeft;
    if (this.normalColValuesDiv)
      this.setPathTitleMarginTop(this.normalColValuesDiv.nativeElement.scrollTop);
  }

  showNote() {
    this.noteOpened$.next(true);
  }

  closeNote() {
    this.noteOpened$.next(false);
  }

  noteChanged(note: string) {
    const newNomNote: string = !util.isNullOrWhitespace(note) ? note.trim() : '';
    this.selectedItemForm.controls['nomNotes'].patchValue(newNomNote);
    this.selectedItemForm.controls['nomNotes'].markAsDirty();
    this.patchModifiedProps(this.selectedItemForm, 'deliveryVol');
    this.closeNote();
  }

  addTransfer() {
    const addDealParams: AddDealParams = {
      selectedDrawerText: 'Transfer Deal',
      supplyCounterpartyId: this.sosItem().supplyCounterpartyId,
      supplyDealType: this.sosItem().dealType,
      receiptPointId: this.sosItem().receiptPointId,
      marketCounterpartyId: null,
      marketDealType: 'Swing',
      transferSourceCounterpartyId: this.sosItem().supplyCounterpartyId,
      transferSourceMeterId: this.sosItem().sourceMeterId,
      sourceTicket: this.sosItem().sourceTicket
    };

    this.addDealParams = addDealParams;

    const cancelFunc = () => {
      this.loadForm.patchValue({ nomDate: this.nomDate }, { emitEvent: false });
    };

    const dontSaveFunc = () => {
      this.addDealOpened$.next(true);
    };

    if (this.isGridDirty())
      util.onDetailChanging(this.itemForms, this.dialogService, dontSaveFunc, this.openSaveNoms, { fnCancelAction: cancelFunc, skipPristine: true });
    else
      dontSaveFunc();
  }

  addDealClosed(isDealAdded: boolean): void {
    this.addDealOpened$.next(false);
    if (isDealAdded) {
      this.notify.success('transfer added');
      this.refreshTransferSet$.next(true);
    }
  }

  static removeTrailingQuestionMarks(value: string): string {
    return value.replace(/\?$/, '');
  }

  static getStringValueFromQueryParams(queryParams: Params, key: string): string | undefined {
    let value: string;

    const queryParam = queryParams[key];
    const isString = typeof queryParam === 'string';
    if (isString) {
      const cleanedValue = SosTransfersComponent.removeTrailingQuestionMarks(queryParam);
      value = cleanedValue ?? undefined;
    }

    return value;
  }

  static getBooleanValueFromQueryParams(queryParams: Params, key: string): boolean | undefined {
    let value: boolean;

    const queryParam = queryParams[key];
    const isString = typeof queryParam === 'string';
    if (isString) {
      const cleanedValue = SosTransfersComponent.removeTrailingQuestionMarks(queryParam);
      const lowerCaseValue = cleanedValue?.toLowerCase();

      if (lowerCaseValue === 'true') {
        value = true;
      } else if (lowerCaseValue === 'false') {
        value = false;
      }
    }

    return value;
  }

  static getIntValueFromQueryParams(queryParams: Params, key: string): number | undefined {
    let value;

    const queryParam = queryParams[key];
    const isString = typeof queryParam === 'string';
    if (isString) {
      const cleanedValue = SosTransfersComponent.removeTrailingQuestionMarks(queryParam);
      const parsedValue = parseInt(cleanedValue, 10);
      value = isNaN(parsedValue) ? undefined : parsedValue;
    }

    return value;
  }

  fillActualItemFromQueryParams(queryParams: Params): void {
    this.actualItem.actualTypeId = SosTransfersComponent.getIntValueFromQueryParams(queryParams, 'actualTypeId');
    this.actualItem.supplyNomId = SosTransfersComponent.getIntValueFromQueryParams(queryParams, 'supplyNomId');
    this.actualItem.marketNomId = SosTransfersComponent.getIntValueFromQueryParams(queryParams, 'marketNomId');
    this.actualItem.lastTransferId = SosTransfersComponent.getIntValueFromQueryParams(queryParams, 'lastTransferId');
    this.actualItem.saveDate = SosTransfersComponent.getStringValueFromQueryParams(queryParams, 'saveDate');
    this.actualItem.nomDate = SosTransfersComponent.getStringValueFromQueryParams(queryParams, 'nomDate');
    this.actualItem.isLinked = SosTransfersComponent.getBooleanValueFromQueryParams(queryParams, 'isLinked');
  }

  fillDealIdFromQueryParams(queryParams: Params) {
    const value = SosTransfersComponent.getIntValueFromQueryParams(queryParams, 'dealId');
    this.dealId = value;
  }

  fillTransferDealIdFromQueryParams(queryParams: Params) {
    const value = SosTransfersComponent.getIntValueFromQueryParams(queryParams, 'transferDealId');
    this.transferDealId = value;
  }

  fillTransferDealDateFromQueryParams(queryParams: Params) {
    let value;

    const queryParam = queryParams['transferDealDate'];
    const isString = typeof queryParam === 'string';
    if (isString) {
      value = SosTransfersComponent.removeTrailingQuestionMarks(queryParam)
    }

    this.transferDealDate = value;
  }

}
