import { Title } from '@angular/platform-browser';
import { tap, take, map, catchError, switchMap, filter, shareReplay, retry } from 'rxjs/operators';
import { of, BehaviorSubject, Subject, combineLatest, Observable, merge } from 'rxjs';
import { State } from '@progress/kendo-data-query';
import { MessageService } from '../_shared/services/message.service';
import { AbstractControl, 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 { PlantStatementService, RequiredData, Item, Statement, CombinedMeter, Calcs, PriceItem, StatementImportResult, PayPtrValues, MeterItem, PayoutTypeItem } from './plantstatement.service';
import { SaveType } from '../_shared/utils/util';
import { CustomFormBuilder } from '../_shared/services/custom-form-builder.service';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, HostListener, inject, ViewChild, ViewEncapsulation } from '@angular/core';
import dayjs from 'dayjs';
import { GridDataResult } from '@progress/kendo-angular-grid';
import { FAST_KENDO_COMMON, FAST_PAGE_COMMON } from '../app.config';
import { FileRestrictions, SelectEvent, SuccessEvent, ErrorEvent } from '@progress/kendo-angular-upload';
import { FastComboboxComponent } from '../_shared/elements/fast-combobox.component';
import { FastNumericTextBoxComponent } from '../_shared/elements/fast-numerictextbox.component';

@Component({
  selector: 'app-plantstatement',
  templateUrl: './plantstatement.component.html',
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [FAST_PAGE_COMMON, FAST_KENDO_COMMON]
})
export class PlantStatementComponent {
  @ViewChild("grid", { read: ElementRef }) kendoGridEl: ElementRef;
  @ViewChild('meterElem') meterElem: FastComboboxComponent<MeterItem[]>;
  @ViewChild('wellheadPtrPriceElem') wellheadPtrPriceElem: FastNumericTextBoxComponent;
  @ViewChild('liquidsTakePercentElem') liquidsTakePercentElem: FastNumericTextBoxComponent;
  @HostListener('window:resize') onResize() {
    //this function is empty but for some reason it helps the window to resize faster
  };

  util = util;
  icons = util.icons;
  importUrl = `${window.location.origin}/api/PlantStatement/Import`;
  importFileRestrictions: FileRestrictions = { allowedExtensions: [".xls", ".xlsx", ".xlsm"] };
  statementForm: util.FormModel<Statement>;
  statementInitialValues: Statement; //the default values for a new statement
  statementOriginalValues: Statement; //the original values of an existing loaded statement
  combinedMeterForm: util.FormModel<CombinedMeter>;
  gridScrollPosition: util.GridScrollPosition = { topPos: 0, leftPos: 0 };
  localRequiredData: RequiredData;
  hasModifyPermission = false;
  mySelection: number[] = [];
  accountingFormatStr = '$ #,.00000;$ (#,.00000)';
  isCombinedStatement: boolean = false;
  volumeInputFormat = '#,;(#,)';
  volumeInputDecimals = 0;
  priceInputFormat = '$ #,.00000;$ (#,.00000)';
  priceInputDecimals = 5;
  decimalInputFormat = '#,.00000;(#,.00000)';
  decimalInputDecimals = 5;
  percentInputFormat = '#,.#####\\%;(#,.#####)\\%';
  percentInputDecimals = 5;
  selectedPriceItem: PriceItem;
  selectedPayoutDescription: string;
  isInitialMeterRefresh: boolean = false;
  combinedEditMeterId: number;

  title$: Observable<string>
  gridLoading$ = new BehaviorSubject<boolean>(true)
  statementLoading$ = new BehaviorSubject<boolean>(true)
  pricesLoading$ = new BehaviorSubject<boolean>(true)
  refreshItems$ = new BehaviorSubject<string>(null)
  exporting$ = new BehaviorSubject<boolean>(false)
  refreshRequiredData$ = new BehaviorSubject(null)
  refreshStatement$ = new BehaviorSubject<number>(null)
  save$ = new Subject<SaveType>()
  delete$ = new Subject()
  exportClicked$ = new Subject()
  metersDialog$ = new BehaviorSubject<boolean>(false)
  statementOpened$ = new BehaviorSubject<boolean>(false)
  saveDialogOpened$ = new BehaviorSubject<boolean>(false)
  combinedMetersOpened$ = new BehaviorSubject<boolean>(false)
  meterOpened$ = new BehaviorSubject<boolean>(false)
  taxesChanged$: Observable<string | number>
  producerChanged$: Observable<number>
  plantChanged$: Observable<number>
  payoutTypeChanged$: Observable<number>
  productionMonthChanged$: Observable<Date>
  statementDateChanged$: Observable<Date>
  priceIdChanged$: Observable<[PriceItem[], number]>
  meterChanged$: Observable<number>
  descriptorChanged$: Observable<number>
  calcs$: Observable<Calcs>
  refreshCalcs$ = new Subject<void>()
  importing$ = new BehaviorSubject<boolean>(false)
  filterPayoutType$ = new BehaviorSubject<string>(null)
  filterProducers$ = new BehaviorSubject<string>(null)
  filterMeters$ = new BehaviorSubject<string>(null)
  filterPlants$ = new BehaviorSubject<string>(null)
  filterPayoutTypes$ = new BehaviorSubject<string>(null)
  filterStatementDescriptors$ = new BehaviorSubject<string>(null)
  filterPrices$ = new BehaviorSubject<string>(null)
  refreshNotes$ = new Subject<Statement>();
  notesLabel$: Observable<string>
  producers$: Observable<util.IdName[]>
  meters$: Observable<MeterItem[]>
  plants$: Observable<util.IdName[]>
  payoutTypes$: Observable<PayoutTypeItem[]>
  statementDescriptors$: Observable<util.IdName[]>
  prices$: Observable<PriceItem[]>
  requiredData$: Observable<RequiredData>
  items$: Observable<GridDataResult>
  exportAction$: Observable<{ fileBlob: Blob, fileName: string }>
  statement$: Observable<Statement>
  saveResult$: Observable<number>
  deleteResult$: Observable<object>
  refreshMetersForPlantAndProducer$ = new Subject<void>()
  subMetersForPlantAndProducer$: Observable<MeterItem[]>
  refreshPrices$ = new Subject<Statement>()
  subPrices$: Observable<PriceItem[]>
  optionRequiredFields$: Observable<string[]>
  refreshOptionRequiredFields$ = new Subject<void>()
  defaultDescriptor$: Observable<number>
  refreshDefaultDescriptor$ = new Subject<Statement>()
  defaultPayPtrValues$: Observable<PayPtrValues>
  refreshDefaultPayPtrValues$ = new Subject<Statement>()

  service = inject(PlantStatementService);
  messageService = inject(MessageService);
  titleService = inject(Title);
  fb = inject(CustomFormBuilder);
  ref = inject(ChangeDetectorRef);
  dialogService = inject(DialogService);
  notify = inject(NotifyService);

  constructor() {
    this.title$ = of('Plant Statement').pipe(
      tap((title) => util.trySetTitle(this.titleService, title))
    );

    this.statementForm = this.getStatementForm()
    this.statementInitialValues = this.statementForm.getRawValue() as Statement;
    this.combinedMeterForm = this.getCombinedMeterForm()

    this.taxesChanged$ = this.statementForm.get('taxes').valueChanges.pipe(
      filter(() => {
        return !this.statementLoading$.value;
      }),
      tap(taxVal => {
        let formattedValue: string = null;
        if (!util.isNullOrWhitespace(taxVal)) {
          const originalTaxStr = taxVal.toString();
          const calculatedValue = util.getCalculatedValue(originalTaxStr, 5);
          formattedValue = util.kendoFormatNumber(calculatedValue, '$ #,.00000;$ (#,.00000)');
        }
        this.statementForm.get('taxes').setValue(formattedValue, { emitEvent: false });
      }),
      shareReplay(1),
      catchError(err => {
        return util.handleError(err, this.messageService)
      }), retry(1)
    );

    this.producerChanged$ = this.statementForm.get('producerId').valueChanges.pipe(
      filter(() => {
        return !this.statementLoading$.value;
      }),
      tap(() => {
        setTimeout(() => { //waits for form controls to update before calling refresh
          const statement = this.statementForm.getRawValue() as Statement;
          this.refreshMetersForPlantAndProducer$.next();
          this.refreshPrices$.next(statement);
          this.refreshDefaultDescriptor$.next(statement);
        });
      }),
      shareReplay(1)
    );

    this.plantChanged$ = this.statementForm.get('plantId').valueChanges.pipe(
      filter(() => {
        return !this.statementLoading$.value;
      }),
      tap(() => {
        setTimeout(() => { //waits for form controls to update before calling refresh
          this.refreshMetersForPlantAndProducer$.next();
          const statement = this.statementForm.getRawValue() as Statement;
          this.refreshNotes$.next(statement);
          this.refreshPrices$.next(statement);
          this.refreshOptionRequiredFields$.next();
          this.refreshDefaultDescriptor$.next(statement);
        });
      }),
      shareReplay(1)
    );

    this.payoutTypeChanged$ = this.statementForm.get('payoutTypeId').valueChanges.pipe(
      filter(() => {
        return !this.statementLoading$.value;
      }),
      tap(payoutTypeId => {
        setTimeout(() => { //waits for form controls to update before calling refresh
          this.setSelectedPayoutDescriptionById(payoutTypeId);
          this.refreshOptionRequiredFields$.next();
        });
      }),
      shareReplay(1)
    );

    this.statementDateChanged$ = this.statementForm.get('statementDate').valueChanges.pipe(
      filter(() => {
        return !this.statementLoading$.value;
      }),
      tap(() => {
        setTimeout(() => { //waits for form controls to update before calling refresh
          const statement = this.statementForm.getRawValue() as Statement;
          this.refreshPrices$.next(statement);
        });
      }),
      shareReplay(1)
    );

    this.productionMonthChanged$ = this.statementForm.get('productionMonth').valueChanges.pipe(
      filter(() => {
        return !this.statementLoading$.value;
      }),
      tap(() => {
        setTimeout(() => { //waits for form controls to update before calling refresh
          const statement = this.statementForm.getRawValue() as Statement;
          this.refreshNotes$.next(statement);
          this.refreshPrices$.next(statement);
          this.refreshDefaultDescriptor$.next(statement);
        });
      }),
      shareReplay(1)
    );

    this.descriptorChanged$ = this.statementForm.get('statementDescriptorId').valueChanges.pipe(
      filter(() => {
        return !this.statementLoading$.value;
      }),
      tap(() => {
        setTimeout(() => { //waits for form controls to update before calling refresh
          const statement = this.statementForm.getRawValue() as Statement;
          this.refreshDefaultPayPtrValues$.next(statement);
        });
      }),
      shareReplay(1)
    );

    this.subMetersForPlantAndProducer$ = this.refreshMetersForPlantAndProducer$.pipe(
      map(() => {
        const selectedSingleMeterId = this.statementForm.controls.meterId.getRawValue();
        const selectedPlantId = this.statementForm.get('plantId').value ?? null;
        const selectedProducerId = this.statementForm.get('producerId').value ?? null;

        const filteredMeterItems = this.localRequiredData.meters.filter(item => {
          const matchesPlant = selectedPlantId === null || item.plantIds.some(itemPlantId => selectedPlantId === itemPlantId);
          const matchesProducer = selectedProducerId === null || item.producerIds.some(itemProducerId => selectedProducerId === itemProducerId);
          return matchesPlant && matchesProducer;
        });

        const multiMeterIds = this.statementForm.controls.combinedMeters.getRawValue().map((item: CombinedMeter) => item.meterId);
        if (this.isInitialMeterRefresh || this.isCombinedStatement) {
          //if the selected meter is not in the filtered meters then add it to the filtered meters as a mismatch
          //if the selected meter is in the filtered meters then remove the word "mismatch" from the filtered meters in case it was previously added
          //we also need to update the meter names in the combined meters form

          let selectedMeterIds: number[] = [];
          if (selectedSingleMeterId)
            selectedMeterIds = [selectedSingleMeterId];
          else if (multiMeterIds.length > 0)
            selectedMeterIds = multiMeterIds;

          selectedMeterIds.forEach(selectedMeterId => {
            const filteredMeterHasSelectedMeter = filteredMeterItems.findIndex(item => item.meterId === selectedMeterId) !== -1;
            if (!filteredMeterHasSelectedMeter) { //add the mismatched meter to the filtered meters dropdown
              //get a copy of the selected meter so that we don't modify the original meter in localRequiredData.meters
              const selectedMeterItem = structuredClone(this.localRequiredData.meters.find(item => item.meterId === selectedMeterId));
              //add the mismatched meter to the filtered meters dropdown
              selectedMeterItem.meterName = "{Mismatch} " + selectedMeterItem.meterName;
              filteredMeterItems.push(selectedMeterItem);
              //update the meter names in the combined meters form
              const combinedMeterItems = this.statementForm.controls.combinedMeters.getRawValue() as CombinedMeter[];
              combinedMeterItems.forEach(combinedMeterItem => {
                if (combinedMeterItem.meterId === selectedMeterId)
                  combinedMeterItem.meterName = selectedMeterItem.meterName;
              });
            }
            else {
              //remove the word mismatch from the filtered meters dropdown in case it was previously added
              const meterItem = filteredMeterItems.find(item => item.meterId === selectedMeterId);
              meterItem.meterName = meterItem.meterName.replace('{Mismatch} ', '');
              //update the meter names in the combined meters form
              const combinedMeterItems = this.statementForm.controls.combinedMeters.getRawValue() as CombinedMeter[];
              combinedMeterItems.forEach(combinedMeterItem => {
                if (combinedMeterItem.meterId === selectedMeterId)
                  combinedMeterItem.meterName = meterItem.meterName;
              });
            }
          });
        }

        const onlyOneItem = filteredMeterItems.length === 1;
        const noItems = filteredMeterItems.length === 0;
        const multipleItems = filteredMeterItems.length > 1;
        const isInitalRefresh = this.isInitialMeterRefresh;
        if (!this.isCombinedStatement) {
          if (onlyOneItem)
            this.statementForm.patchValue({ meterId: filteredMeterItems[0].meterId }, { emitEvent: false }); //set the only item
          else if (noItems)
            this.statementForm.patchValue({ meterId: null }, { emitEvent: false }); //clear the value
          else if (multipleItems && !isInitalRefresh)
            this.statementForm.patchValue({ meterId: null }, { emitEvent: false }); //clear the value
          else {
            //do nothing since there are multiple items, this is the initial refresh, and the meterId is already set
          }
        }

        this.isInitialMeterRefresh = false;
        return filteredMeterItems;
      }),
      shareReplay(1),
      catchError(err => {
        return util.handleError(err, this.messageService)
      })
    );

    this.subPrices$ = this.refreshPrices$.pipe(
      filter(() => {
        return !this.statementLoading$.value;
      }),
      switchMap(statement => {
        this.pricesLoading$.next(true);
        this.statementForm.patchValue({ pricesId: null }, { emitEvent: false });
        this.selectedPriceItem = null;

        this.ref.detectChanges();
        const productionMonth = statement.productionMonth;
        const statementDate = statement.statementDate;
        const plantId = statement.plantId;
        const producerId = statement.producerId;

        //using != instead of !== to check for null and undefined
        const hasValues: boolean = productionMonth != null && statementDate != null && plantId != null && producerId != null;
        if (!hasValues)
          return of([]);
        else
          return this.service.getPlantStatementPrices(productionMonth, statementDate, plantId, producerId);
      }),
      tap(prices => {
        const originalSelectedPricesId = this.statementForm.get('pricesId').value ?? null;
        let finalSelectedPricesId: number = null;

        if (prices.length === 1)
          finalSelectedPricesId = prices[0].id;
        else if (prices.length > 1) {
          const hasSelectedPricesId = prices.findIndex(item => item.id === originalSelectedPricesId) !== -1;
          const defaultPricesId = prices.find(item => item.isDefault)?.id ?? null;
          finalSelectedPricesId = hasSelectedPricesId ? originalSelectedPricesId : defaultPricesId;
        }

        this.statementForm.patchValue({ pricesId: finalSelectedPricesId }, { emitEvent: true });
        this.pricesLoading$.next(false);
      }),
      shareReplay(1),
      catchError(err => {
        return util.handleError(err, this.messageService)
      })
    );

    this.priceIdChanged$ = combineLatest([this.subPrices$, this.statementForm.get('pricesId').valueChanges]).pipe(
      filter(() => {
        return !this.statementLoading$.value;
      }),
      tap(([prices, pricesId]) => {
        this.selectedPriceItem = prices.find(item => item.id === pricesId);
      }),
      shareReplay(1)
    );

    this.meterChanged$ = this.statementForm.get('meterId').valueChanges.pipe(
      filter(() => {
        return !this.statementLoading$.value;
      }),
      tap(() => {
        setTimeout(() => { //waits for form controls to update before calling refresh
          const statement = this.statementForm.getRawValue() as Statement;
          this.refreshDefaultDescriptor$.next(statement);
        });
      }),
      shareReplay(1)
    );

    this.defaultDescriptor$ = this.refreshDefaultDescriptor$.pipe(
      filter(statement => {
        //using != instead of !== to check for null and undefined
        const hasValues: boolean = statement.productionMonth != null && statement.plantId != null && statement.producerId != null;
        return (!this.statementLoading$.value && hasValues);
      }),
      switchMap(statement => {
        const productionMonth = statement.productionMonth;
        const plantId = statement.plantId;
        const producerId = statement.producerId;
        const meterId = statement.meterId;
        return this.service.getDefaultDescriptorId(productionMonth, plantId, producerId, meterId);
      }),
      tap(defaultDescriptorId => {
        //do emit event so that defaultValues$ is triggered by the descriptor change
        this.statementForm.patchValue({ statementDescriptorId: defaultDescriptorId });
      }),
      shareReplay(1),
      catchError(err => {
        return util.handleError(err, this.messageService)
      })
    );

    this.defaultPayPtrValues$ = this.refreshDefaultPayPtrValues$.pipe(
      filter(statement => {
        //using != instead of !== to check for null and undefined
        const hasValues: boolean = statement.productionMonth != null && statement.plantId != null && statement.producerId != null;
        return (!this.statementLoading$.value && hasValues);
      }),
      switchMap(statement => {
        const productionMonth = statement.productionMonth;
        const plantId = statement.plantId;
        const producerId = statement.producerId;
        const meterId = statement.meterId;
        const descriptorId = statement.statementDescriptorId;
        return this.service.getDefaultPayPtrValues(productionMonth, plantId, producerId, meterId, descriptorId);
      }),
      tap(payPtrValues => {
        this.statementForm.patchValue({
          payoutTypeId: payPtrValues?.payoutTypeId,
          ptrTrans: payPtrValues?.ptrTrans,
          ptrFuelPercent: payPtrValues?.ptrFuelPercent
        }, { emitEvent: false });
      }),
      shareReplay(1),
      catchError(err => {
        return util.handleError(err, this.messageService)
      })
    );

    this.notesLabel$ = this.refreshNotes$.pipe(
      filter(statement => {
        //using != instead of !== to check for null and undefined
        const hasValues: boolean = statement.productionMonth != null && statement.statementDate != null && statement.plantId != null && statement.producerId != null;
        return (!this.statementLoading$.value && hasValues);
      }),
      switchMap(statement => {
        const productionMonth = statement.productionMonth;
        const plantId = statement.plantId;
        return combineLatest([of(statement), this.service.getPlantNotes(plantId, productionMonth)]);
      }),
      map(([statement, notes]) => {
        const monthStr = dayjs(statement.productionMonth).format('MMMM YYYY');
        const plantId = statement.plantId
        const plantstr = this.localRequiredData.plants.find(plant => plant.id === plantId);
        const plantStr = plantstr ? plantstr.name : '';
        const plantNotesLabel = `Plant Notes for ${monthStr}, ${plantStr}`;
        this.statementForm.patchValue({ plantNotes: notes }, { emitEvent: false });
        return plantNotesLabel;
      }),
      shareReplay(1),
      catchError(err => {
        return util.handleError(err, this.messageService)
      })
    );

    this.calcs$ = merge(this.refreshCalcs$, this.statementForm.valueChanges).pipe(
      filter(() => {
        return !this.statementLoading$.value;
      }),
      switchMap(() => {
        const statement = this.statementForm.getRawValue() as Statement;
        const calcs: Calcs = { feesTotal: 0, allocatedTotal: 0, recoveredTotal: 0, theoreticalTotal: 0, gpmTotal: 0, totalPvrMcf: 0, totalPtrMmbtu: 0, processedBtuFactor: 0, wellheadBtuFactor: 0 }
        calcs.feesTotal = this.getFeesTotal(statement);
        calcs.allocatedTotal = this.getAllocatedTotal(statement);
        calcs.recoveredTotal = this.getRecoveredTotal(statement);
        calcs.theoreticalTotal = this.getTheoreticalTotal(statement);
        calcs.gpmTotal = this.getGpmTotal(statement);
        calcs.totalPvrMcf = this.getTotalPvrMcf(statement);
        calcs.totalPtrMmbtu = this.getTotalPtrMmbtu(statement);
        calcs.processedBtuFactor = this.getProcessedBtuFactor(statement);
        calcs.wellheadBtuFactor = this.getWellheadBtuFactor(statement);
        return of(calcs);
      }),
      catchError(err => {
        return util.handleError(err, this.messageService)
      }),
      shareReplay(1)
    );

    this.optionRequiredFields$ = this.refreshOptionRequiredFields$.pipe(
      filter(() => {
        //using != instead of !== to check for null and undefined
        return !this.statementLoading$.value;
      }),
      switchMap(() => {
        const statement = this.statementForm.getRawValue() as Statement;
        const plantId = statement.plantId;
        const payoutTypeId = statement.payoutTypeId;

        if (plantId != null)
          return this.service.getOptionRequiredFields(plantId, payoutTypeId);
        else
          return of(['productionMonth', 'statementDate', 'plantId', 'producerId']);
      }),
      tap(requiredFields => {
        //remove all individual control errors and validators
        const formControls: { [key: string]: AbstractControl } = this.statementForm.controls;
        Object.keys(formControls).forEach((key) => {
          const formControl = formControls[key];
          formControl.setErrors(null); //remove errors since validators are changing
          formControl.clearValidators(); //remove all validators (.e.g required fields)
        });

        requiredFields.forEach(requiredField => {
          const formControl = this.statementForm.get(requiredField);
          formControl.setValidators(Validators.required);
          formControl.updateValueAndValidity({ emitEvent: false });
        });

        this.statementForm.updateValueAndValidity({ emitEvent: false });
      }),
      shareReplay(1),
      catchError(err => {
        return util.handleError(err, this.messageService)
      })
    );

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

    this.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)
    );

    this.exportAction$ = this.exportClicked$.pipe(
      tap(() => {
        this.exporting$.next(true);
      }),
      switchMap(() => {
        return this.service.exportItems(this.state, 'PlantStatement.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.statement$ = this.refreshStatement$.pipe(
      filter(id => id !== null),
      tap(() => {
        this.statementLoading$.next(true);
        util.saveGridScrollPos(this.kendoGridEl, this.gridScrollPosition);
        this.statementForm.disable();
        this.statementForm.reset();
        this.statementOpened$.next(true);
      }),
      switchMap(id => {
        if (id === 0)
          return of(this.statementInitialValues);
        else
          return this.service.getStatement(id);
      }),
      map(result => {
        const statement: Statement = result;
        if (statement) {
          util.convertToDates(statement);
          if (typeof statement.taxes === 'number')
            statement.taxes = util.kendoFormatNumber(statement.taxes, this.accountingFormatStr)
        }
        return statement;
      }),
      tap(result => {
        this.isInitialMeterRefresh = true;
        this.statementForm.setValue(result);
        this.statementOriginalValues = structuredClone(result);
        this.statementForm.enable();
        this.isCombinedChanged(result.combinedMeters?.length > 0); //also triggers refreshMetersForPlantAndProducer$
        this.setSelectedPayoutDescriptionById(result.payoutTypeId);
        this.statementLoading$.next(false);
        this.refreshNotes$.next(result);
        this.volumePercentCalc(result.combinedMeters);
        this.refreshCalcs$.next();
        this.refreshPrices$.next(result);
        this.refreshOptionRequiredFields$.next();
        util.focusInputTarget();
      }),
      shareReplay(1),
      catchError(err => {
        this.closeStatement(false);
        return util.handleError(err, this.messageService)
      }), retry(10)
    );

    this.saveResult$ = this.save$.pipe(
      filter(saveType => {
        this.statementForm.markAllAsTouched();
        const isSameProdMonth = dayjs(this.statementForm.value.productionMonth).isSame(dayjs(this.statementOriginalValues.productionMonth));
        const isSameStateDate = dayjs(this.statementForm.value.statementDate).isSame(dayjs(this.statementOriginalValues.statementDate));
        const isSamePlant = this.statementForm.value.plantId === this.statementOriginalValues.plantId;
        const isSameProducer = this.statementForm.value.producerId === this.statementOriginalValues.producerId;
        const isSameMeter = this.statementForm.value.meterId === this.statementOriginalValues.meterId;
        const hasSameValues = isSameProdMonth && isSameStateDate && isSamePlant && isSameProducer && isSameMeter;
        const isFormValid = this.statementForm.valid;
        if (saveType == util.SaveType.New && hasSameValues) {
          this.notify.error("Please change the dates or other info to save new");
          return false;
        }
        else if (!isFormValid) {
          this.notify.error("validation failed");
          return false;
        }
        else {
          return true;
        }
      }),
      switchMap(saveType => {
        this.statementLoading$.next(true);
        this.statementForm.disable();
        const itemToSave = this.statementForm.getRawValue() as Statement;
        if (typeof itemToSave.taxes === 'string')
          itemToSave.taxes = util.getCalculatedValue(itemToSave.taxes, 5);
        if (!this.isCombinedStatement)
          itemToSave.combinedMeters = [];
        return this.service.saveStatement(itemToSave, saveType);
      }),
      tap(saveResult => {
        this.notify.success('save successful');
        this.closeStatement(false);
        this.refreshItems$.next(null);
        this.mySelection = [saveResult];
      }),
      shareReplay(1),
      catchError(err => {
        this.statementForm.enable();
        this.statementLoading$.next(false);
        return util.handleError(err, this.messageService)
      }), retry(10)
    );

    this.deleteResult$ = this.delete$.pipe(
      switchMap(() => {
        this.statementLoading$.next(true);
        this.statementForm.disable();
        const itemToDelete: Statement = this.statementForm.getRawValue();
        return this.service.deleteStatement(itemToDelete.id);
      }),
      tap(() => {
        this.notify.success('delete successful');
        this.closeStatement(false);
        this.refreshItems$.next(null);
      }),
      shareReplay(1),
      catchError(err => {
        this.statementForm.enable();
        this.statementLoading$.next(false);
        return util.handleError(err, this.messageService)
      }), retry(10)
    );

    this.producers$ = this.filterProducers$.pipe(util.filterIdNames(this.statementLoading$, this.requiredData$, 'producers'));
    this.meters$ = this.filterMeters$.pipe(util.filterSpecials(this.statementLoading$, this.subMetersForPlantAndProducer$, null, 'meterName'));
    this.plants$ = this.filterPlants$.pipe(util.filterIdNames(this.statementLoading$, this.requiredData$, 'plants'));
    this.payoutTypes$ = this.filterPayoutTypes$.pipe(util.filterSpecials(this.statementLoading$, this.requiredData$, 'payoutTypes', 'name'));
    this.statementDescriptors$ = this.filterStatementDescriptors$.pipe(util.filterIdNames(this.statementLoading$, this.requiredData$, 'statementDescriptors'));
    this.prices$ = this.filterPrices$.pipe(util.filterSpecials(this.statementLoading$, this.subPrices$, null, 'name'));
  }

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

  getStatementForm() {
    const fb = this.fb;
    //required fields are set by optionRequiredFields$
    const fg: util.FormModel<Statement> = fb.group({
      id: fb.ctr(0),
      productionMonth: fb.ctr(util.currentDate.date(1).toDate()),
      statementDate: fb.ctr(util.currentDate.toDate()),
      plantId: fb.ctr(null),
      statementNotes: fb.ctr(null),
      plantNotes: fb.ctr(null),
      producerId: fb.ctr(null),
      meterId: fb.ctr(null),
      statementDescriptorId: fb.ctr(null),
      payoutTypeId: fb.ctr(null),
      wellheadVolumeMcf: fb.ctr(null),
      wellheadVolumeMmbtu: fb.ctr(null),
      processedVolumeMcf: fb.ctr(null),
      processedVolumeMmbtu: fb.ctr(null),
      shrinkMcf: fb.ctr(null),
      shrinkMmbtu: fb.ctr(null),
      plantFuelMcf: fb.ctr(null),
      plantFuelMmbtu: fb.ctr(null),
      flareMcf: fb.ctr(null),
      flareMmbtu: fb.ctr(null),
      bypassMcf: fb.ctr(null),
      bypassMmbtu: fb.ctr(null),
      plantGainLossMcf: fb.ctr(null),
      plantGainLossMmbtu: fb.ctr(null),
      inKindPvrMcf: fb.ctr(null),
      inKindPtrMmbtu: fb.ctr(null),
      recoveredCarbonDioxide: fb.ctr(null),
      recoveredEthane: fb.ctr(null),
      recoveredPropane: fb.ctr(null),
      recoveredIsoButane: fb.ctr(null),
      recoveredNormalButane: fb.ctr(null),
      recoveredNaturalGasoline: fb.ctr(null),
      recoveredScrubber: fb.ctr(null),
      theoreticalCarbonDioxide: fb.ctr(null),
      theoreticalEthane: fb.ctr(null),
      theoreticalPropane: fb.ctr(null),
      theoreticalIsoButane: fb.ctr(null),
      theoreticalNormalButane: fb.ctr(null),
      theoreticalIsoPentane: fb.ctr(null),
      theoreticalNormalPentane: fb.ctr(null),
      theoreticalNaturalGasoline: fb.ctr(null),
      ptrCashoutPrice: fb.ctr(null),
      ptrTrans: fb.ctr(null),
      ptrFuelPercent: fb.ctr(null),
      wellheadPtrPrice: fb.ctr(null),
      liquidsTakePercent: fb.ctr(null),
      taxes: fb.ctr(null, { updateOn: 'blur' }),
      fractionationFee: fb.ctr(null),
      plantProcessingFee: fb.ctr(null),
      carbonDioxideFee: fb.ctr(null),
      compressionFee: fb.ctr(null),
      electricityFee: fb.ctr(null),
      stabilizationFee: fb.ctr(null),
      miscellaneousFee: fb.ctr(null),
      liquidsLiftingFee: fb.ctr(null),
      gpmCarbonDioxide: fb.ctr(null),
      gpmEthane: fb.ctr(null),
      gpmPropane: fb.ctr(null),
      gpmIsoButane: fb.ctr(null),
      gpmNormalButane: fb.ctr(null),
      gpmIsoPentane: fb.ctr(null),
      gpmNormalPentane: fb.ctr(null),
      gpmHexanePlus: fb.ctr(null),
      molePercentNitrogen: fb.ctr(null),
      molePercentCarbonDioxide: fb.ctr(null),
      molePercentMethane: fb.ctr(null),
      molePercentEthane: fb.ctr(null),
      molePercentPropane: fb.ctr(null),
      molePercentIsoButane: fb.ctr(null),
      molePercentNormalButane: fb.ctr(null),
      molePercentIsoPentane: fb.ctr(null),
      molePercentNormalPentane: fb.ctr(null),
      molePercentHexane: fb.ctr(null),
      molePercentHeptane: fb.ctr(null),
      pipelineGainLossMcf: fb.ctr(null),
      pipelineGainLossMmbtu: fb.ctr(null),
      allocatedCarbonDioxide: fb.ctr(null),
      allocatedEthane: fb.ctr(null),
      allocatedPropane: fb.ctr(null),
      allocatedIsoButane: fb.ctr(null),
      allocatedNormalButane: fb.ctr(null),
      allocatedNaturalGasoline: fb.ctr(null),
      allocatedScrubber: fb.ctr(null),
      pricesId: fb.ctr(null),
      chargeProducerFee: fb.ctr(true),
      electionMcf: fb.ctr(null),
      electionMmbtu: fb.ctr(null),
      combinedMeters: fb.ctr([])
    });

    return fg;
  }

  getCombinedMeterForm() {
    const fb = this.fb;
    const fg1: util.FormModel<CombinedMeter> = fb.group({
      meterName: fb.ctr(null, Validators.required),
      meterId: fb.ctr(null, Validators.required),
      wellheadVolumeMcf: fb.ctr(null),
      wellheadVolumeMmbtu: fb.ctr(null, Validators.required),
      pipelineVolume: fb.ctr(null),
      wellheadPtrPrice: fb.ctr(null),
      liquidsTakePercent: fb.ctr(null),
      inKindPvrMcf: fb.ctr(null),
      inKindPtrMmbtu: fb.ctr(null),
      volumePercent: fb.ctr(null, Validators.required)
    });
    return fg1
  }

  onStatementClosing() {
    util.onDetailChanging(this.statementForm, this.dialogService, this.closeStatement, this.openSaveDialog, { skipPristine: true })
  }

  closeStatement = (isFromInterface: boolean) => {
    this.statementOpened$.next(false);
    this.saveDialogOpened$.next(false);

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

  addStatement(): void {
    this.refreshStatement$.next(0);
  }

  editStatement(dataItem: Item): void {
    this.refreshStatement$.next(dataItem.Id);
  }

  addMeter(): void {
    this.combinedEditMeterId = 0;
    this.meterOpened$.next(true);
    this.combinedMeterForm.reset();
    util.focusInputTarget();
  }

  editMeter(dataItem: CombinedMeter): void {
    this.combinedEditMeterId = dataItem.meterId;
    this.combinedMeterForm.setValue(dataItem);
    this.meterOpened$.next(true);
    util.focusInputTarget();
  }

  removeMeter(): void {
    const deleteConfirmSettings: DialogSettings = {
      title: "Please confirm",
      content: "Are you sure you want to delete this meter 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) {
        const meterIdToRemove = this.combinedMeterForm.value.meterId;
        let combinedMeterItems = structuredClone(this.statementForm.get('combinedMeters').value);
        combinedMeterItems = combinedMeterItems.filter(item => item.meterId !== meterIdToRemove);
        this.statementForm.patchValue({ combinedMeters: combinedMeterItems }, { emitEvent: false });
        this.closeMeter();
      }
    });
  }

  deleteStatement(): 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);
  }

  getWellheadBtuFactor(statement: Statement) {
    if (statement.wellheadVolumeMmbtu && statement.wellheadVolumeMcf)
      return statement.wellheadVolumeMmbtu / statement.wellheadVolumeMcf;
    else
      return 0;
  };

  getProcessedBtuFactor(statement: Statement) {
    if (statement.processedVolumeMmbtu && statement.processedVolumeMcf)
      return statement.processedVolumeMmbtu / statement.processedVolumeMcf;
    else
      return 0;
  };

  getTotalPtrMmbtu(statement: Statement) {
    const plantGainLossMmbtu = (statement.plantGainLossMmbtu || 0) * -1;
    const pipelineGainLossMmbtu = (statement.pipelineGainLossMmbtu || 0) * -1;
    const totalPtrMmbtu =
      (statement.shrinkMmbtu || 0) +
      (statement.plantFuelMmbtu || 0) +
      (statement.flareMmbtu || 0) +
      (statement.bypassMmbtu || 0) +
      (statement.electionMmbtu || 0) +
      plantGainLossMmbtu +
      pipelineGainLossMmbtu;

    return totalPtrMmbtu;
  }

  getTotalPvrMcf(statement: Statement) {
    const plantGainLossMcf = (statement.plantGainLossMcf || 0) * -1;
    const pipelineGainLossMcf = (statement.pipelineGainLossMcf || 0) * -1;
    const totalPvrMcf =
      (statement.shrinkMcf || 0) +
      (statement.plantFuelMcf || 0) +
      (statement.flareMcf || 0) +
      (statement.bypassMcf || 0) +
      (statement.electionMcf || 0) +
      plantGainLossMcf +
      pipelineGainLossMcf;

    return totalPvrMcf;
  }

  getGpmTotal(statement: Statement) {
    const gpmTotal =
      (statement.gpmCarbonDioxide || 0) +
      (statement.gpmEthane || 0) +
      (statement.gpmPropane || 0) +
      (statement.gpmIsoButane || 0) +
      (statement.gpmNormalButane || 0) +
      (statement.gpmIsoPentane || 0) +
      (statement.gpmNormalPentane || 0) +
      (statement.gpmHexanePlus || 0);

    return gpmTotal;
  }

  getTheoreticalTotal(statement: Statement) {
    const theoreticalTotal =
      (statement.theoreticalCarbonDioxide || 0) +
      (statement.theoreticalEthane || 0) +
      (statement.theoreticalPropane || 0) +
      (statement.theoreticalIsoButane || 0) +
      (statement.theoreticalNormalButane || 0) +
      (statement.theoreticalIsoPentane || 0) +
      (statement.theoreticalNormalPentane || 0) +
      (statement.theoreticalNaturalGasoline || 0);

    return theoreticalTotal;
  }

  getRecoveredTotal(statement: Statement) {
    const recoveredTotal =
      (statement.recoveredCarbonDioxide || 0) +
      (statement.recoveredEthane || 0) +
      (statement.recoveredPropane || 0) +
      (statement.recoveredIsoButane || 0) +
      (statement.recoveredNormalButane || 0) +
      (statement.recoveredNaturalGasoline || 0) +
      (statement.recoveredScrubber || 0);

    return recoveredTotal;
  }

  getAllocatedTotal(statement: Statement) {
    const allocatedTotal =
      (statement.allocatedCarbonDioxide || 0) +
      (statement.allocatedEthane || 0) +
      (statement.allocatedPropane || 0) +
      (statement.allocatedIsoButane || 0) +
      (statement.allocatedNormalButane || 0) +
      (statement.allocatedNaturalGasoline || 0) +
      (statement.allocatedScrubber || 0);

    return allocatedTotal;
  }

  getFeesTotal(statement: Statement) {
    const taxes: number = typeof statement.taxes === 'string' ? util.getCalculatedValue(statement.taxes, 5) : statement.taxes;
    const feesTotal =
      (taxes || 0) +
      (statement.fractionationFee || 0) +
      (statement.plantProcessingFee || 0) +
      (statement.carbonDioxideFee || 0) +
      (statement.compressionFee || 0) +
      (statement.electricityFee || 0) +
      (statement.stabilizationFee || 0) +
      (statement.miscellaneousFee || 0) +
      (statement.liquidsLiftingFee || 0);

    return feesTotal;
  }

  setCombinedEditMeter(): void {
    this.combinedMeterForm.markAllAsTouched();
    const combinedMeterItems = structuredClone(this.statementForm.get('combinedMeters').value);
    const isNewMeter = this.combinedEditMeterId === 0;

    const newMeterItem: CombinedMeter = this.combinedMeterForm.value as CombinedMeter
    if (newMeterItem.meterId) {
      newMeterItem.meterName = this.localRequiredData.meters.find(item => item.meterId === newMeterItem.meterId).meterName;

      if (isNewMeter)
        combinedMeterItems.push(newMeterItem);
      else {
        const existingMeterItem = combinedMeterItems.find(item => item.meterId === this.combinedEditMeterId);
        combinedMeterItems.splice(combinedMeterItems.indexOf(existingMeterItem), 1, newMeterItem);
      }

      this.volumePercentCalc(combinedMeterItems);
      this.combinedMeterForm.setValue(newMeterItem);
    }

    const hasDuplicates = combinedMeterItems.some((item, index) => {
      return combinedMeterItems.findIndex((item2, index2) => {
        return item.meterId === item2.meterId && index !== index2;
      }) !== -1;
    });

    if (hasDuplicates)
      this.notify.error("Meter already exists");
    else if (this.combinedMeterForm.valid) {
      this.closeMeter();
      this.statementForm.patchValue({ combinedMeters: combinedMeterItems }, { emitEvent: false });
    }
    else
      this.notify.error("validation failed");
  }

  volumePercentCalc(combinedMeters: CombinedMeter[]): void {
    const totalVol = combinedMeters.reduce((result: number, combinedItem) => {
      if (combinedItem.pipelineVolume) {
        return result + (Math.abs(combinedItem.pipelineVolume) ?? 0);
      } else {
        return result + (Math.abs(combinedItem.wellheadVolumeMmbtu) ?? 0);
      }
    }, 0)
    combinedMeters.forEach((item) => {
      if (totalVol === 0) {
        item.volumePercent = 100;
      } else
        if (item.pipelineVolume) {
          item.volumePercent = Math.abs(item.pipelineVolume) / totalVol * 100;
        } else {
          item.volumePercent = (Math.abs(item.wellheadVolumeMmbtu) ?? 0) / totalVol * 100;

        }
    });
  };

  openCombinedMeters(): void {
    this.combinedMetersOpened$.next(true);
  }

  closeCombinedMeters(): void {
    this.combinedMetersOpened$.next(false);
  }

  closeMeter(): void {
    this.meterOpened$.next(false);
  }

  openSaveDialog = () => {
    this.statementForm.markAllAsTouched();
    const isFormValid = this.statementForm.valid;
    if (!isFormValid)
      this.notify.error("validation failed");
    else {
      if (this.statementForm.value.id === 0)
        this.save$.next(util.SaveType.New);
      else
        this.saveDialogOpened$.next(true);
    }
  }

  closeSaveDialog(): void {
    this.saveDialogOpened$.next(false);
  }

  importSelect(value: SelectEvent) {
    if (value?.files?.[0]?.validationErrors?.[0] === "invalidFileExtension")
      this.notify.error("Invalid file type");
  }

  importStart() {
    this.importing$.next(true);
  }

  importCompleted(e: SuccessEvent) {
    this.importing$.next(false);
    const result: StatementImportResult = e.response.body;
    const message = result.changesMessage + '\r\n' + (result.notes ?? '');
    this.messageService.info(message);
    this.refreshItems$.next(null);
  }

  importError(e: ErrorEvent) {
    this.importing$.next(false);
    this.messageService.throw(e.response);
  }

  isCombinedChanged(value: boolean) {
    this.isCombinedStatement = value;
    if (value) { //combined
      this.statementForm.patchValue({ meterId: null, wellheadPtrPrice: null, liquidsTakePercent: null }, { emitEvent: false });
      this.statementForm.controls.meterId.disable();
      this.statementForm.controls.wellheadPtrPrice.disable();
      this.statementForm.controls.liquidsTakePercent.disable();

      if (this.meterElem)
        this.meterElem.placeholder.set('Combined');
      if (this.wellheadPtrPriceElem)
        this.wellheadPtrPriceElem.placeholder.set('Combined');
      if (this.liquidsTakePercentElem)
        this.liquidsTakePercentElem.placeholder.set('Combined');
    }
    else { //not combined
      this.statementForm.controls.meterId.enable();
      this.statementForm.controls.wellheadPtrPrice.enable();
      this.statementForm.controls.liquidsTakePercent.enable();

      if (this.meterElem)
        this.meterElem.placeholder.set('');
      if (this.wellheadPtrPriceElem)
        this.wellheadPtrPriceElem.placeholder.set('');
      if (this.liquidsTakePercentElem)
        this.liquidsTakePercentElem.placeholder.set('');
    }
    this.refreshMetersForPlantAndProducer$.next();
  }

  setSelectedPayoutDescriptionById(payoutTypeId: number) {
    const description = this.localRequiredData.payoutTypes.find(item => item.id === payoutTypeId)?.description;
    this.selectedPayoutDescription = util.isNullOrWhitespace(description) ? 'no description available for selection' : description;
  }
}

