import { Component, ChangeDetectionStrategy, ViewChild, ElementRef, HostListener, ViewEncapsulation, OnInit, signal, inject } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { tap, map, catchError, switchMap, filter, shareReplay, retry, debounceTime } from 'rxjs/operators';
import { of, BehaviorSubject, Subject, combineLatest, Observable } from 'rxjs';
import { State, process } from '@progress/kendo-data-query';
import { MessageService } from '../_shared/services/message.service';
import { CustomFormBuilder } from '../_shared/services/custom-form-builder.service';
import { NotifyService } from '../_shared/services/notify.service';
import * as util from '../_shared/utils/util';
import { ActualsCrudeService, Item, RequiredData, UserSelections, CellEditItem, ExternalParams } from './actuals-crude.service';
import dayjs from 'dayjs';
import { SaveType } from '../_shared/utils/util';
import { CellClickEvent, CellCloseEvent, RowClassArgs } from '@progress/kendo-angular-grid';
import { ContextMenuSelectEvent, KENDO_CONTEXTMENU } from '@progress/kendo-angular-menu';
import { ActualsCrudeHistoryComponent as ActualsCrudeHistoryComponent } from '../actuals-crude-history/actuals-crude-history.component';
import { ActualsCrudeHistoryService } from '../actuals-crude-history/actuals-crude-history.service';
import { FormsModule, Validators } from '@angular/forms';
import { FAST_KENDO_COMMON, FAST_PAGE_COMMON } from '../app.config';
import { NgTemplateOutlet, DecimalPipe } from '@angular/common';
import { Dictionary } from '../_shared/utils/dictionary';
import { CommonService } from '../_shared/services/common.service';
import { environment } from '../../environments/environment';
import { BigNumber } from 'decimal-eval';
import { FastGridComponent } from '../_shared/elements/fast-grid.component';
import { FastTipDirective } from '../_shared/elements/fast-tip.directive';
import { PipeIconComponent } from "../_shared/elements/pipe-icon.component";

enum contextMenuEnum {
  CopyDown,
  CopyUp
}

interface ContextMenuItem {
  enum: contextMenuEnum;
  text: string;
}

type editableFieldName = 'actualVolume' | 'price' | 'priceAdj' | 'actualFee' | 'adjustment' | 'transportRate';

@Component({
  selector: 'app-actuals-crude',
  templateUrl: './actuals-crude.component.html',
  styles: `
    @reference "tailwindcss";

    .modifiedVolume .editedVolumeCell,
    .modifiedPrice .editedPriceCell,
    .modifiedTransportRate .editedTransportRateCell,
    .modifiedPriceAdj .editedPriceAdjCell,
    .modifiedActualFee .editedActualFeeCell,
    .modifiedAdjustment .editedAdjustmentCell,
    .modifiedLink .editedLinkedCell {
      @apply bg-blue-800 font-bold text-white;
    }

    /* Unmodified cells (transparent background) */
    .unmodifiedVolume .editedVolumeCell,
    .unmodifiedPrice .editedPriceCell,
    .unmodifiedTransportRate .editedTransportRateCell,
    .unmodifiedPriceAdj .editedPriceAdjCell,
    .unmodifiedActualFee .editedActualFeeCell,
    .unmodifiedAdjustment .editedAdjustmentCell,
    .unmodifiedLink .editedLinkedCell {
      @apply bg-transparent;
    }

    .volumeVariance .editedVolumeCell {
      @apply bg-yellow-300 text-black;
    }
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None,
  standalone: true,
  imports: [FAST_KENDO_COMMON, FAST_PAGE_COMMON, KENDO_CONTEXTMENU, ActualsCrudeHistoryComponent, NgTemplateOutlet, DecimalPipe, FormsModule, PipeIconComponent],
})
export class ActualsCrudeComponent implements OnInit {
  @ViewChild('grid', { read: ElementRef }) kendoGridEl: ElementRef;
  @ViewChild('grid') fastGrid: FastGridComponent;
  @ViewChild('history') historyElem: ActualsCrudeHistoryComponent;
  @HostListener('window:resize') onResize() {
    //this function is empty but for some reason it helps the window to resize faster
  }
  @HostListener('window:beforeunload') canDeactivate(): Observable<boolean> | boolean {
    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);
    }
    //true will navigate without confirmation; false will show a confirm dialog before navigating away
    return !this.isGridDirty();
  }
  isGridDirty(): boolean {
    const gridDirty = this.gridItems().some(item => this.isItemDirty(item));
    return gridDirty;
  }

  isItemDirty(item: Item): boolean {
    return item.isVolumeEdited || item.isPriceEdited || item.isPriceAdjEdited || item.isTransportRateEdited || item.isLinkEdited || item.isActualFeeEdited || item.isAdjustmentEdited;
  }

  actualTypeId: number;
  supplyNomId: number;
  marketNomId: number;
  lastTransferId: number;
  date: Date;
  clickedTransferDealId: number;
  clickedTransferDay: number;
  clickedItem: Item;
  dealUrl: string;
  schedulingUrl: string;
  screenPathsResult$: Observable<Dictionary<string>>;
  historyItem: Item;

  service = inject(ActualsCrudeService);
  historyService = inject(ActualsCrudeHistoryService);
  messageService = inject(MessageService);
  titleService = inject(Title);
  fb = inject(CustomFormBuilder);
  notify = inject(NotifyService);
  commonService = inject(CommonService);

  constructor() {
    const paths = this.commonService.screenPaths();
    this.dealUrl = paths.get('deal');
    this.schedulingUrl = paths.get('sos crude transfers');

    // switch to local url if testing locally
    if (!environment.production) {
      this.dealUrl = `https://127.0.0.1:54330/#/Deal`;
      this.schedulingUrl = `https://127.0.0.1:54328/#/SOSCrudeTransfers`;
    }
    ;
  }

  util = util;
  gridItems = signal<Item[]>([]);
  filteredGridItems: Item[] = [];
  userSelectionsForm = this.getUserSelectionsForm();
  editedCellForm = this.getEditedCellForm();
  gridScrollPosition: util.GridScrollPosition = { topPos: 0, leftPos: 0 };
  localRequiredData: RequiredData;
  hasModifyPermission = false;
  gridLoading$ = new BehaviorSubject<boolean>(false)
  refreshItems$ = new BehaviorSubject<string>(null)
  refreshActualizeNoms$ = new BehaviorSubject<string>(null)
  exporting$ = new BehaviorSubject<boolean>(false)
  refreshRequiredData$ = new BehaviorSubject(null)
  refreshCounterpartyForPipeline$ = new BehaviorSubject(null)
  refreshMeterForPipeline$ = new BehaviorSubject(null)
  refreshContractForPipeline$ = new BehaviorSubject(null)
  refreshHistory$ = new Subject<Item>()
  setTneMeter$ = new Subject<void>();
  historyOpened$ = new BehaviorSubject<boolean>(false)
  historyLoading$ = new BehaviorSubject<boolean>(false)
  lastClickedCell: { rowIndex: number, columnIndex: number };
  save$ = new Subject<SaveType>()
  toggleTneMeter$ = new Subject<void>();
  refreshGridSettings$ = new BehaviorSubject<string>(null)
  delete$ = new Subject()
  exportClicked$ = new Subject()
  saveGridSettings$ = new Subject()
  dealsOpened$ = new BehaviorSubject<boolean>(false);
  transfersOpened$ = new BehaviorSubject<boolean>(false);
  tneMeterOpened$ = new BehaviorSubject<boolean>(false);
  cellContextItems: CellClickEvent;
  refreshContextMenuFilter$ = new Subject<number>();
  windowWidth: number;
  colIndexTneMeter = 7;
  colIndexActualVolume = 10;
  colIndexPrice = 11;
  colIndexPriceAdj = 12;
  colIndexActualFee = 13;
  colIndexAdjustment = 14
  colIndexTransportRate = 15;
  colIndexLink: number = null;
  colIndexHistory = 18;
  nomVolTotal = signal(0);
  actualVolTotal = signal(0);
  amountTotal = signal(0);
  gridSaveDate: Date = dayjs().startOf('day').toDate();
  dateSaveOpened$ = new BehaviorSubject(false);
  externalParams: ExternalParams;
  isEditingNextCell = false;

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

  contextMenuItems: ContextMenuItem[] = [
    { enum: contextMenuEnum.CopyDown, text: 'Copy Down' },
    { enum: contextMenuEnum.CopyUp, text: 'Copy Up' }
  ];

  ngOnInit(): void {
    this.refreshGridSettings$.next(null);
  }

  getTransferDealDate(): string {
    const productionMonth = this.userSelectionsForm.get('productionPeriod').value;

    const year = dayjs(productionMonth).year();
    const month = dayjs(productionMonth).month() + 1; // month() returns 0-11, so add 1
    const day = 1;
    const transferDealDate = dayjs(`${year}-${month}-${day}`).format('YYYY-MM-DD');
    return transferDealDate;
  }

  getSchedulingUrl(): string {
    let url: string = 'about:blank';

    if (this.schedulingUrl != null) {
      const transferDealDate = this.getTransferDealDate();

      url = `${this.schedulingUrl}?transferDealId=${this.clickedTransferDealId}&transferDealDate=${transferDealDate}`;
    }

    console.log(`scheduling URL: ${url}`);
    return url;
  }

  getTneMeterUrl(): string {
    let url: string = 'about:blank';

    if (this.schedulingUrl != null && this.clickedItem != null) {
      url = `${this.schedulingUrl}?actualTypeId=${this.clickedItem.actualTypeId}`;
      url += `&supplyNomId=${this.clickedItem.supplyNomId}`;
      url += `&marketNomId=${this.clickedItem.marketNomId}`;

      if (this.clickedItem.lastTransferId != null) {
        url += `&lastTransferId=${this.clickedItem.lastTransferId}`;
      }

      const saveDate = dayjs(this.clickedItem.saveDate).format('YYYY-MM-DD');
      url += `&saveDate=${saveDate}`;

      const nomDate = this.getTransferDealDate();
      url += `&nomDate=${nomDate}`;

      url += `&isLinked=${this.clickedItem.isLinked}`;
    }

    console.log(`tne meter URL: ${url} `);
    return url;
  }

  getUserSelectionsForm() {
    const fb = this.fb;
    const defaultMonth = dayjs().startOf('month').add(-1, 'month').toDate();
    const fg: util.FormModel<UserSelections> = fb.group({
      productionPeriod: fb.ctr(defaultMonth, Validators.required),
      actualTypeId: fb.ctr(1, Validators.required),
      pipelineId: fb.ctr(0, Validators.required),
      pipeContractId: fb.ctr(0, Validators.required),
      meterId: fb.ctr(0, Validators.required),
      counterpartyId: fb.ctr(null, Validators.required),
    });

    return fg;
  }

  getEditedCellForm() {
    const fb = this.fb;
    const fg1: util.FormModel<CellEditItem> = fb.group({
      actualVolume: fb.ctr(null),
      price: fb.ctr(null),
      priceAdj: fb.ctr(null),
      actualFee: fb.ctr(null),
      adjustment: fb.ctr(null),
      transportRate: fb.ctr(null),
      isLinked: fb.ctr(null),
    });
    return fg1;
  }

  rowCallback = (context: RowClassArgs) => {
    const classes: { [key: string]: boolean } = {};
    const dataItem = context.dataItem;

    const hasVolumeVariance = dataItem.actualVolume != null && dataItem.actualVolume !== dataItem.nomVolume;

    if (dataItem.isVolumeEdited)
      classes['modifiedVolume'] = true;
    else if (hasVolumeVariance)
      classes['volumeVariance'] = true;
    else
      classes['unmodifiedVolume'] = true;

    if (dataItem.isPriceEdited)
      classes['modifiedPrice'] = true;
    else
      classes['unmodifiedPrice'] = true;

    if (dataItem.isPriceAdjEdited)
      classes['modifiedPriceAdj'] = true;
    else
      classes['unmodifiedPriceAdj'] = true;

    if (dataItem.isTransportRateEdited)
      classes['modifiedTransportRate'] = true;
    else
      classes['unmodifiedTransportRate'] = true

    if (dataItem.isLinkEdited)
      classes['modifiedLink'] = true;
    else
      classes['unmodifiedLink'] = true;

    if (dataItem.isActualFeeEdited)
      classes['modifiedActualFee'] = true;
    else
      classes['unmodifiedActualFee'] = true;

    if (dataItem.isAdjustmentEdited)
      classes['modifiedAdjustment'] = true;
    else
      classes['unmodifiedAdjustment'] = true;

    return classes;
  };

  onHistoryMouseOver(e: MouseEvent, gridItem: Item, tooltip: FastTipDirective) {
    const mousedElement = e.target as HTMLElement;
    this.refreshHistory$.next(gridItem);
    tooltip.show(mousedElement);
  }

  onDealMouseover(e: MouseEvent, tooltip: FastTipDirective) {
    const mousedElement = e.target as HTMLElement;
    tooltip.show(mousedElement);
  }

  onCellClick(args: CellClickEvent) {
    const dataItem = args.dataItem as Item;
    const isLeftClick = args.originalEvent.button === 0;
    const isRightClick = args.originalEvent.button === 2;
    const isBuy = this.actualTypeId === 1;
    const isTneMeterColumn = args.columnIndex === this.colIndexTneMeter;
    const isPriceColumn = args.columnIndex === this.colIndexPrice;
    const isPriceAdj = args.columnIndex === this.colIndexPriceAdj;
    const isActualFee = args.columnIndex === this.colIndexActualFee;
    const isAdjustment = args.columnIndex === this.colIndexAdjustment;
    const isTransportRateColumn = args.columnIndex === this.colIndexTransportRate;
    const isActualVolumeColumn = args.columnIndex === this.colIndexActualVolume;
    const isLinkedColumn = args.columnIndex === this.colIndexLink;
    const isHistoryColumn = args.columnIndex === this.colIndexHistory && isLeftClick;
    const isTransportPurchaseWithTransfers = isBuy && isTransportRateColumn && dataItem.hasTransfers;
    const isEditableTransportRateCell = isTransportRateColumn && !isTransportPurchaseWithTransfers;
    const isEditableColumn = isActualVolumeColumn || isPriceColumn || isEditableTransportRateCell || isPriceAdj || isLinkedColumn || isActualFee || isAdjustment;
    const isEditing = this.fastGrid.grid.isEditingCell();
    const newLinkValue = !isEditing ? dataItem.isLinked : !this.editedCellForm.value.isLinked;

    if (isLeftClick && isEditableColumn && !isEditing)
      this.editGridCell(args);

    if (isLeftClick && isActualVolumeColumn)
      this.editedCellForm.patchValue({ actualVolume: dataItem.actualVolume });
    else if (isLeftClick && isPriceColumn)
      this.editedCellForm.patchValue({ price: dataItem.price });
    else if (isLeftClick && isPriceAdj)
      this.editedCellForm.patchValue({ priceAdj: dataItem.priceAdj });
    else if (isLeftClick && isActualFee)
      this.editedCellForm.patchValue({ actualFee: dataItem.actualFee });
    else if (isLeftClick && isAdjustment)
      this.editedCellForm.patchValue({ adjustment: dataItem.adjustment });
    else if (isLeftClick && isEditableTransportRateCell)
      this.editedCellForm.patchValue({ transportRate: dataItem.transportRate });
    else if (isLeftClick && isLinkedColumn)
      this.editedCellForm.patchValue({ isLinked: newLinkValue });
    else if (isLeftClick && isHistoryColumn)
      this.openHistory(dataItem);
    else if (isLeftClick && isBuy && isTneMeterColumn)
      this.onTneMeterClicked(dataItem);
    else if (isRightClick && isEditableColumn)
      this.cellContextItems = args;
  }

  editGridCell(args: CellClickEvent) {
    this.fastGrid.grid.editCell(args.rowIndex, args.columnIndex, this.editedCellForm);
    this.lastClickedCell = { rowIndex: args.rowIndex, columnIndex: args.columnIndex };
  }

  onKeyDown(args: KeyboardEvent) {
    args.stopImmediatePropagation();

    const isEnterPressed = args.key === 'Enter';
    // exit if enter was not pressed or if we are still in the middle of editing a cell
    if (!isEnterPressed || this.isEditingNextCell)
      return;

    if (!this.filteredGridItems) return;

    const isLastRow = this.lastClickedCell.rowIndex === this.filteredGridItems.length - 1;
    const dataItem = isLastRow ? this.filteredGridItems[0] : this.filteredGridItems[this.lastClickedCell.rowIndex + 1];

    this.editNextGridCell();
    this.patchEditedCellForm(dataItem);
    this.setNextClickedRowIndex();
  }

  patchEditedCellForm(dataItem: Item) {
    const isPriceCell = this.lastClickedCell.columnIndex === this.colIndexPrice;
    const isPriceAdjCell = this.lastClickedCell.columnIndex === this.colIndexPriceAdj;
    const isActualFeeCell = this.lastClickedCell.columnIndex === this.colIndexActualFee;
    const isAdjustmentCell = this.lastClickedCell.columnIndex === this.colIndexAdjustment;
    const isTransportRateCell = this.lastClickedCell.columnIndex === this.colIndexTransportRate;
    const isActualVolumeCell = this.lastClickedCell.columnIndex === this.colIndexActualVolume;
    const isEditableCell = isActualVolumeCell || isPriceCell || isPriceAdjCell || isActualFeeCell || isAdjustmentCell || isTransportRateCell;
    if (!isEditableCell)
      return;

    if (isActualVolumeCell)
      this.editedCellForm.patchValue({ actualVolume: dataItem.actualVolume });
    else if (isPriceCell)
      this.editedCellForm.patchValue({ price: dataItem.price });
    else if (isPriceAdjCell)
      this.editedCellForm.patchValue({ priceAdj: dataItem.priceAdj });
    else if (isActualFeeCell)
      this.editedCellForm.patchValue({ actualFee: dataItem.actualFee });
    else if (isAdjustmentCell)
      this.editedCellForm.patchValue({ adjustment: dataItem.adjustment });
    else if (isTransportRateCell)
      this.editedCellForm.patchValue({ transportRate: dataItem.transportRate });
  }

  editNextGridCell() {
    if (!this.filteredGridItems) return;

    this.isEditingNextCell = true;
    const isLastRow = this.lastClickedCell.rowIndex === this.filteredGridItems.length - 1;
    const nextRow = isLastRow ? 0 : this.lastClickedCell.rowIndex + 1;
    if (isLastRow)
      this.fastGrid.grid.scrollTo({ row: 0, column: this.lastClickedCell.columnIndex });

    this.fastGrid.grid.editCell(nextRow, this.lastClickedCell.columnIndex, this.editedCellForm);
    setTimeout(() => {
      //we need a delay here if we want holding the 'enter' key down to work with virtualization
      //virtual page changes while scrolling cause the cell to close, exiting edit mode
      //we need to put the the cell back into edit mode after the page change in onGridStateChange
      //onGridStateChange won't know we were in the middle of editing a cell unless we delay here
      this.isEditingNextCell = false;
    }, 20);
  }

  setNextClickedRowIndex() {
    if (!this.filteredGridItems) return;

    const isLastRow = this.lastClickedCell.rowIndex === this.filteredGridItems.length - 1;
    this.lastClickedCell.rowIndex = isLastRow ? 0 : this.lastClickedCell.rowIndex + 1;
  }

  onCellClose(args: CellCloseEvent) {
    const closedCellItem = args.dataItem;
    const currentItemVolume = closedCellItem.actualVolume;
    const currentItemPrice = closedCellItem.price;
    const currentItemPriceAdj = closedCellItem.priceAdj;
    const currentItemActualFee = closedCellItem.actualFee;
    const currentItemAdjustment = closedCellItem.adjustment;
    const currentItemTransportRate = closedCellItem.transportRate;
    const isActualVolumeCell = args.column.field === 'actualVolume'
    const isPriceCell = args.column.field === 'price'
    const isPriceAdjCell = args.column.field === 'priceAdj';
    const isActualFeeCell = args.column.field === 'actualFee';
    const isAdjustmentCell = args.column.field === 'adjustment';
    const isTransportRateColumn = args.column.field === 'transportRate';
    const isLinkedColumn = args.column.field === 'isLinked';
    const modifiedCellItem = this.editedCellForm.getRawValue() as CellEditItem;

    if (isActualVolumeCell && modifiedCellItem.actualVolume === currentItemVolume) {
      this.editedCellForm.reset();
    }
    else if (isActualVolumeCell && modifiedCellItem.actualVolume !== currentItemVolume) {
      closedCellItem.actualVolume = modifiedCellItem.actualVolume;
      closedCellItem.isVolumeEdited = true;
      this.editedCellForm.reset();
    }
    else if (isPriceCell && modifiedCellItem.price === currentItemPrice) {
      this.editedCellForm.reset();
    }
    else if (isPriceCell && modifiedCellItem.price !== currentItemPrice) {
      closedCellItem.price = modifiedCellItem.price;
      closedCellItem.isPriceEdited = true;
      this.editedCellForm.reset();
    }
    else if (isPriceAdjCell && modifiedCellItem.priceAdj === currentItemPriceAdj) {
      this.editedCellForm.reset();
    }
    else if (isPriceAdjCell && modifiedCellItem.priceAdj !== currentItemPriceAdj) {
      closedCellItem.priceAdj = modifiedCellItem.priceAdj;
      closedCellItem.isPriceAdjEdited = true;
      this.editedCellForm.reset();
    }
    else if (isTransportRateColumn && modifiedCellItem.transportRate === currentItemTransportRate) {
      this.editedCellForm.reset();
    }
    else if (isTransportRateColumn && modifiedCellItem.transportRate !== currentItemTransportRate) {
      closedCellItem.transportRate = modifiedCellItem.transportRate;
      closedCellItem.isTransportRateEdited = true;
      this.editedCellForm.reset();
    }
    else if (isActualFeeCell && modifiedCellItem.actualFee === currentItemActualFee) {
      this.editedCellForm.reset();
    }
    else if (isActualFeeCell && modifiedCellItem.actualFee !== currentItemActualFee) {
      closedCellItem.actualFee = modifiedCellItem.actualFee;
      closedCellItem.isActualFeeEdited = true;
      this.editedCellForm.reset();
    }
    else if (isAdjustmentCell && modifiedCellItem.adjustment === currentItemAdjustment) {
      this.editedCellForm.reset();
    }
    else if (isAdjustmentCell && modifiedCellItem.adjustment !== currentItemAdjustment) {
      closedCellItem.adjustment = modifiedCellItem.adjustment;
      closedCellItem.isAdjustmentEdited = true;
      this.editedCellForm.reset();
    }
    else if (isLinkedColumn && modifiedCellItem.isLinked === closedCellItem.isLinked) {
      this.editedCellForm.reset();
    }
    else if (isLinkedColumn && modifiedCellItem.isLinked !== closedCellItem.isLinked) {
      closedCellItem.isLinked = modifiedCellItem.isLinked;
      closedCellItem.isLinkEdited = true;
      this.editedCellForm.reset();
    }
    this.calculateColumnTotals();
  }

  onContextMenuSelect(e: ContextMenuSelectEvent) {
    let fieldName: editableFieldName;
    const colIndex = this.cellContextItems.columnIndex;

    if (colIndex === this.colIndexActualVolume)
      fieldName = 'actualVolume';
    else if (colIndex === this.colIndexPrice)
      fieldName = 'price';
    else if (colIndex === this.colIndexPriceAdj)
      fieldName = 'priceAdj';
    else if (colIndex === this.colIndexActualFee)
      fieldName = 'actualFee';
    else if (colIndex === this.colIndexAdjustment)
      fieldName = 'adjustment';
    else if (colIndex === this.colIndexTransportRate)
      fieldName = 'transportRate';

    this.copyData(fieldName, e.item.enum);
  }

  copyData(fieldName: editableFieldName, contextSelection: contextMenuEnum) {
    const rowIndex = this.cellContextItems.rowIndex;
    if (!this.filteredGridItems) return;

    const dataValue = this.filteredGridItems[rowIndex][fieldName];

    let itemsToFill: Item[] = [];
    if (contextSelection === contextMenuEnum.CopyDown)
      itemsToFill = this.filteredGridItems.slice(rowIndex + 1);
    else if (contextSelection === contextMenuEnum.CopyUp)
      itemsToFill = this.filteredGridItems.slice(0, rowIndex);

    itemsToFill.forEach(item => {
      if (fieldName !== "transportRate" || !item.hasTransfers)
        item[fieldName] = dataValue;

      if (fieldName === 'actualVolume')
        item.isVolumeEdited = true;
      if (fieldName === 'price')
        item.isPriceEdited = true;
      if (fieldName === 'priceAdj')
        item.isPriceAdjEdited = true;
      if (fieldName === 'actualFee')
        item.isActualFeeEdited = true;
      if (fieldName === 'adjustment')
        item.isAdjustmentEdited = true;
      if (fieldName === 'transportRate' && !item.hasTransfers)
        item.isTransportRateEdited = true;
    });

    this.calculateColumnTotals();
  }

  onActualizeNomsClicked() {
    if (!this.filteredGridItems) return;

    this.filteredGridItems.forEach(item => {
      if (item.actualVolume == null && item.nomVolume !== undefined) {
        item.isVolumeEdited = true;
        item.actualVolume = item.nomVolume;
      }
    });
    this.calculateColumnTotals();
  }

  save(saveType: util.SaveType): void {
    this.save$.next(saveType);
    util.saveGridScrollPos(this.kendoGridEl, this.gridScrollPosition);
  }

  openSaveWindow() {
    if (!this.gridItems() || this.gridItems().length === 0) {
      this.notify.error("No data to save.");
      return;
    }
    if (this.isGridDirty()) {
      this.dateSaveOpened$.next(true);
      return;
    }
    this.notify.error("No changes made. Please make changes before saving.");
    return;
  }

  closeSaveWindow() {
    this.dateSaveOpened$.next(false);
  }

  saveHistory(): void {
    this.historyElem.saveHistory(this.historyElem, util.SaveType.New);
  }

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

  onGridStateChange(state: State) {
    if (this.isEditingNextCell) {
      //this puts a cell back into edit mode after a virtual page change caused it to close
      const dataItem = this.filteredGridItems[this.lastClickedCell.rowIndex];
      this.fastGrid.grid.editCell(this.lastClickedCell.rowIndex, this.lastClickedCell.columnIndex, this.editedCellForm);
      this.patchEditedCellForm(dataItem);
    }

    if (this.hasStateChanged(state)) {
      this.state = state;
      this.saveGridSettings$.next(null);
      this.filteredGridItems = this.getFilteredGridItems();
      this.calculateColumnTotals();
    }
  }

  /// Compare old state and new state, but only compare the filter and sort properties.
  private hasStateChanged(newState: State): boolean {
    const oldState = this.state;
    if (!oldState) {
      return true;
    }

    const oldStateObj = { filter: oldState.filter, sort: oldState.sort };
    const newStateObj = { filter: newState.filter, sort: newState.sort };

    const oldStateStr = JSON.stringify(oldStateObj);
    const newStateStr = JSON.stringify(newStateObj);
    const value = oldStateStr !== newStateStr;
    return value;
  }

  getFilteredGridItems(): Item[] {
    const filteredItems = process(this.gridItems(), {
      filter: this.state.filter,
      sort: this.state.sort
    }).data;

    return filteredItems;
  }

  calculateInvoicePrices() {
    if (!this.gridItems()) return;

    this.gridItems.update(items => {
      items.forEach(item => {
        const priceExists = item.price !== null;
        const priceAdjExists = item.priceAdj !== null;
        const transportRateExists = item.transportRate !== null;
        if (priceExists && priceAdjExists && transportRateExists) {
          const price = BigNumber(item.price);
          const priceAdj = BigNumber(item.priceAdj);
          const transportRate = BigNumber(item.transportRate);
          item.invoicePrice = BigNumber.sum(price, priceAdj, transportRate.negated()).toNumber();
        }
      });
      return items;
    });
  }

  calculateRowAmounts() {
    if (!this.gridItems()) return;

    this.gridItems.update(items => {
      items.forEach(item => {
        const volume = item.actualVolume !== null ? item.actualVolume : (item.nomVolume ?? 0);
        if (item.invoicePrice !== null)
          item.amount = (volume * item.invoicePrice) + item.adjustment + item.actualFee;
      });
      return items;
    });
  }

  calculateColumnTotals() {
    //ensure that gridItems signal is updated before calculating totals
    this.gridItems.set([...this.gridItems()]);

    this.calculateInvoicePrices();
    this.calculateRowAmounts();
    if (!this.filteredGridItems) return;

    let amountTotal = 0;
    let nomVolTotal = 0;
    let actualVolTotal = 0;

    this.filteredGridItems.forEach(item => {
      //per client request:
      //For Crude, the total amount should be the sum of the rounded amounts for each row
      //For Gas, the total amount should be the sum of the full non-rounded amounts for each row
      amountTotal += util.round(item.amount ?? 0, 2);
      nomVolTotal += item.nomVolume ?? 0;
      actualVolTotal += item.actualVolume ?? 0;
    });

    this.nomVolTotal.set(nomVolTotal);
    this.actualVolTotal.set(actualVolTotal);
    this.amountTotal.set(amountTotal);
  }

  openHistory(gridItem: Item) {
    this.historyItem = gridItem;
    this.historyOpened$.next(true);
  }

  closeHistory() {
    this.historyOpened$.next(false);
  }

  historyItems$ = this.refreshHistory$.pipe(
    tap(() => {
      this.historyLoading$.next(true);
    }),
    switchMap((item) => {
      return this.historyService.getHistoryItems(item.actualTypeId, item.supplyNomId, item.marketNomId, item.lastTransferId);
    }),
    tap(() => {
      this.historyLoading$.next(false);
    }),
    shareReplay(1),
    catchError(err => {
      return util.handleError(err, this.messageService)
    }), retry(3)
  )

  requiredData$ = this.refreshRequiredData$.pipe(
    tap(() => this.gridLoading$.next(true)),
    switchMap(refreshType => {
      return combineLatest([this.service.requiredData$, of(refreshType)]);
    }),
    map(([requiredData]) => {
      this.localRequiredData = requiredData;
      return requiredData;
    }),
    tap((requiredData) => {
      this.gridLoading$.next(false);
      this.hasModifyPermission = requiredData.hasModifyPermission;
      this.refreshCounterpartyForPipeline$.next(null);
      this.refreshMeterForPipeline$.next(null);
      this.refreshContractForPipeline$.next(null);
    }),
    shareReplay(1),
    catchError(err => {
      return util.handleError(err, this.messageService)
    }), retry(3)
  )

  items$ = this.refreshItems$.pipe(
    filter(() => {
      this.userSelectionsForm.updateValueAndValidity();
      const isValid = this.userSelectionsForm.valid;
      if (!isValid) {
        this.gridItems.set([]);
        this.calculateColumnTotals();
      }
      return isValid;
    }),
    tap(() => {
      this.gridLoading$.next(true);
    }),
    switchMap(() => {
      const userSelections = this.userSelectionsForm.getRawValue() as UserSelections;
      this.actualTypeId = userSelections.actualTypeId;
      this.state.filter = null;
      return this.service.getItems(userSelections);
    }),
    tap((result) => {
      const isTransfer = this.actualTypeId === 0;
      const isBuy = this.actualTypeId === 1;
      const isSell = this.actualTypeId === -1;
      util.convertToDates(result);
      if (isBuy && result.length !== 0) {
        this.colIndexActualVolume = 10;
        this.colIndexPrice = 11;
        this.colIndexPriceAdj = 12;
        this.colIndexActualFee = 13;
        this.colIndexAdjustment = 14;
        this.colIndexTransportRate = 15;
        this.colIndexLink = null;
        this.colIndexHistory = 18;
        this.gridItems.set(result);
        this.gridLoading$.next(false);
      }
      else if (isSell && result.length !== 0) {
        this.colIndexActualVolume = 9;
        this.colIndexPrice = 10;
        this.colIndexPriceAdj = 11;
        this.colIndexActualFee = 12;
        this.colIndexAdjustment = 13;
        this.colIndexTransportRate = null;
        this.colIndexLink = null;
        this.colIndexHistory = 16;
        this.gridItems.set(result);
        this.gridLoading$.next(false);
      }
      else if (isTransfer && result.length !== 0) {
        this.colIndexActualVolume = 7;
        this.colIndexPrice = null;
        this.colIndexPriceAdj = null;
        this.colIndexActualFee = null;
        this.colIndexAdjustment = null;
        this.colIndexTransportRate = 8;
        this.colIndexLink = 9;
        this.colIndexHistory = 10;
        this.gridItems.set(result);
        this.gridLoading$.next(false);
      }
      else if (result.length === 0) {
        this.gridItems.set([]);
        this.gridLoading$.next(false);
      }
      this.filteredGridItems = this.getFilteredGridItems();
      this.refreshActualizeNoms$.next(null);
      this.calculateColumnTotals();
      util.goToSavedGridScrollPos(this.kendoGridEl, this.gridScrollPosition);
      this.refreshContextMenuFilter$.next(this.actualTypeId);
    }),
    shareReplay(1),
    catchError(err => {
      return util.handleError(err, this.messageService);
    }), retry(3)
  )

  gridSettings$ = this.refreshGridSettings$.pipe(
    switchMap(() => {
      return this.service.getGridSettings();
    }),
    tap((result) => {
      if (!result)
        return;

      const parsedState = JSON.parse(result.state) as State;
      if (!parsedState)
        return;

      // we don't want to use the stored take, skip, or filter values, so we ignore/override those values from parsedState
      const take = this.state.take;
      const skip = this.state.skip;
      const filter = this.state.filter;
      this.state = { filter: filter, sort: parsedState.sort, group: parsedState.group, take: take, skip: skip };
    }),
    shareReplay(1),
    catchError(err => {
      return util.handleError(err, this.messageService);
    }), retry(3)
  )

  contextMenuFilter$ = this.refreshContextMenuFilter$.pipe(
    map((actualTypeId: number) => {
      const activeFilters: string[] = [];
      const baseSelector = ".k-grid-content tbody tr[role='row']";

      // Always include the Actual Volume column
      activeFilters.push(
        `${baseSelector} td:nth-child(${this.colIndexActualVolume + 1})`
      );

      if (actualTypeId !== 0) {
        activeFilters.push(
          `${baseSelector} td:nth-child(${this.colIndexPrice + 1})`
        );
        activeFilters.push(
          `${baseSelector} td:nth-child(${this.colIndexPriceAdj + 1})`
        );
        activeFilters.push(
          `${baseSelector} td:nth-child(${this.colIndexActualFee + 1})`
        );
        activeFilters.push(
          `${baseSelector} td:nth-child(${this.colIndexAdjustment + 1})`
        );
      }

      if (actualTypeId !== -1) {
        activeFilters.push(
          `${baseSelector} td:nth-child(${this.colIndexTransportRate + 1})`
        );
      }

      return activeFilters.join(", ");
    })
  );


  isActualizeNomsActive$ = this.refreshActualizeNoms$.pipe(
    map(() => {
      const isMeterSelected = this.userSelectionsForm.get('meterId').value !== null && this.userSelectionsForm.get('meterId').value !== undefined && this.userSelectionsForm.get('meterId').value !== 0;
      const isPipelineSelected = this.userSelectionsForm.get('pipelineId').value !== null && this.userSelectionsForm.get('pipelineId').value !== undefined && this.userSelectionsForm.get('pipelineId').value !== 0;
      const isPipeContractSelected = this.userSelectionsForm.get('pipeContractId').value !== null && this.userSelectionsForm.get('pipeContractId').value !== undefined && this.userSelectionsForm.get('pipeContractId').value !== 0;
      const hasModifyPermission = this.hasModifyPermission;
      const isBuySelected = this.userSelectionsForm.get('actualTypeId').value === 1;
      const isSellOrTransferSelected = this.userSelectionsForm.get('actualTypeId').value === 0 || this.userSelectionsForm.get('actualTypeId').value === -1;
      const isCounterpartySelected = this.userSelectionsForm.get('counterpartyId').value !== null && this.userSelectionsForm.get('counterpartyId').value !== undefined && this.userSelectionsForm.get('counterpartyId').value !== 0;
      const validBuySelections = isBuySelected && isCounterpartySelected && (isPipelineSelected || isMeterSelected);
      const validSellOrTransferSelections = isSellOrTransferSelected && isPipelineSelected && (isPipeContractSelected || isMeterSelected);
      const hasItems = this.filteredGridItems && this.filteredGridItems.length > 0;

      if (hasModifyPermission && hasItems && (validBuySelections || validSellOrTransferSelections))
        return true;
      else
        return false;
    }),
    shareReplay(1),
    catchError(err => {
      return util.handleError(err, this.messageService)
    }), retry(3)
  )

  exportAction$ = this.exportClicked$.pipe(
    tap(() => {
      this.exporting$.next(true);
    }),
    switchMap(() => {
      const userSelectionsValid = this.userSelectionsForm.valid;
      if (userSelectionsValid) {
        const userSelections = this.userSelectionsForm.getRawValue() as UserSelections;
        return this.service.exportItems(userSelections);
      }
      else
        return of(undefined);
    }),
    tap(res => {
      if (res === undefined) {
        this.exporting$.next(false);
        return;
      }
      else {
        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(3)
  )

  saveResult$ = this.save$.pipe(
    switchMap(() => {
      this.fastGrid.grid.closeCell();
      this.gridLoading$.next(true);
      const itemsToSave: Item[] = [];
      this.gridItems().forEach(item => {
        if (this.isItemDirty(item)) {
          const newItem = { ...item };
          newItem.saveDate = this.gridSaveDate;
          itemsToSave.push(newItem);
        }
      });
      this.dateSaveOpened$.next(false);
      return this.service.saveItems(this.actualTypeId, itemsToSave);
    }),
    tap(() => {
      this.notify.success('save successful');
      this.refreshItems$.next(null);
    }),
    shareReplay(1),
    catchError(err => {
      this.gridLoading$.next(false);
      return util.handleError(err, this.messageService)
    }), retry(3)
  )

  saveGridSettingsAction$ = this.saveGridSettings$.pipe(
    debounceTime(1000),
    filter(() => !this.gridLoading$.value),
    switchMap(() => {
      return this.service.saveGridSettings(this.state);
    }),
    shareReplay(1),
    catchError(err => {
      return util.handleError(err, this.messageService)
    })
  )

  subMeterForPipeline$ = this.refreshMeterForPipeline$.pipe(
    filter(() => {
      return this.localRequiredData !== undefined;
    }),
    map(() => {
      if (this.userSelectionsForm.get('meterId').value !== 0)
        this.userSelectionsForm.get('meterId').setValue(0);

      const selectedPipeId = this.userSelectionsForm.get('pipelineId').value ?? null;
      const meters = this.localRequiredData.meters;
      if (selectedPipeId > 0) {
        const allOption = meters.find(m => m.pipeId === 0);
        const modifiedMeters = meters.filter(m => m.pipeId === selectedPipeId);
        modifiedMeters.unshift(allOption);
        return modifiedMeters;
      }
      else {
        return meters;
      }
    }),
    shareReplay(1)
  )

  subContractForPipeline$ = this.refreshContractForPipeline$.pipe(
    filter(() => {
      return this.localRequiredData !== undefined;
    }),
    map(() => {
      if (this.userSelectionsForm.get('pipeContractId').value !== 0)
        this.userSelectionsForm.get('pipeContractId').setValue(0);

      const pipelineId = this.userSelectionsForm.get('pipelineId').value ?? null;
      const pipeContracts = this.localRequiredData.pipeContracts;
      if (pipelineId > 0) {
        const allOption = pipeContracts.find(m => m.pipeId === 0);
        const modifiedPipeContracts = pipeContracts.filter(m => m.pipeId === pipelineId);
        modifiedPipeContracts.unshift(allOption);
        return modifiedPipeContracts;
      }
      else {
        return pipeContracts;
      }
    }),
    shareReplay(1)
  )

  subCounterpartyForPipeline$ = this.refreshCounterpartyForPipeline$.pipe(
    filter(() => {
      return this.localRequiredData !== undefined;
    }),
    map(() => {
      const pipelineId = this.userSelectionsForm.get('pipelineId').value;
      const isPipelineAll = pipelineId === 0;
      const isPipelineEmpty = pipelineId === null || pipelineId === undefined;
      const counterparties = this.localRequiredData.counterparties;
      if (isPipelineAll || isPipelineEmpty) {
        const modifiedCounterparties = counterparties.filter(c => c.counterpartyId > 0);
        return modifiedCounterparties;
      }
      else
        return counterparties;
    }),
    shareReplay(1)
  )

  productionPeriodChanged$ = this.userSelectionsForm.controls['productionPeriod'].valueChanges.pipe(
    tap(() => {
      this.gridSaveDate = dayjs().startOf('day').toDate();
      this.refreshItems$.next(null);
    }),
    shareReplay(1)
  )

  actualTypeChanged$ = this.userSelectionsForm.controls['actualTypeId'].valueChanges.pipe(
    tap(() => {
      this.refreshItems$.next(null);
    }),
    shareReplay(1)
  )

  pipelineChanged$ = this.userSelectionsForm.controls['pipelineId'].valueChanges.pipe(
    tap(pipelineId => {
      this.refreshContractForPipeline$.next(null);
      this.refreshCounterpartyForPipeline$.next(null);
      this.refreshMeterForPipeline$.next(null);

      const counterpartyId = this.userSelectionsForm.get('counterpartyId').value;
      const isPipelineEmpty = pipelineId == null || pipelineId == undefined;
      const isPipelineAll = pipelineId == 0;
      const isPipelineSingle = pipelineId > 0;
      const isCounterpartyEmpty = counterpartyId == null || counterpartyId == undefined;
      const isCounterpartyAll = counterpartyId == 0;
      const isCounterpartySingle = counterpartyId > 0;

      const setPipelineToAll = () => { this.userSelectionsForm.get('pipelineId').patchValue(0, { emitEvent: false }); }
      const setCounterpartyToAll = () => { this.userSelectionsForm.get('counterpartyId').patchValue(0, { emitEvent: false }); }
      const setCounterpartyToEmpty = () => { this.userSelectionsForm.get('counterpartyId').patchValue(null, { emitEvent: false }); }

      if (isPipelineEmpty && isCounterpartySingle)
        setPipelineToAll();
      else if (isPipelineSingle && isCounterpartyEmpty)
        setCounterpartyToAll();
      else if (isPipelineAll && isCounterpartyAll)
        setCounterpartyToEmpty();

      this.refreshItems$.next(null);
    }),
    shareReplay(1)
  )

  pipeContractChanged$ = this.userSelectionsForm.controls['pipeContractId'].valueChanges.pipe(
    tap((pipeContractId) => {
      const isPipeContractEmpty = pipeContractId == null || pipeContractId == undefined;
      const isSpecificPipeContract = pipeContractId > 0;
      const setPipeContractToAll = () => { this.userSelectionsForm.get('pipeContractId').patchValue(0, { emitEvent: false }); }
      const setSpecificPipeline = () => {
        const pipeForContract = this.localRequiredData.pipeContracts.find(c => c.pipeContractId == pipeContractId).pipeId;
        this.userSelectionsForm.get('pipelineId').patchValue(pipeForContract, { emitEvent: false });
        this.refreshCounterpartyForPipeline$.next(null);
      }

      if (isSpecificPipeContract)
        setSpecificPipeline();

      if (isPipeContractEmpty)
        setPipeContractToAll();

      this.refreshItems$.next(null);
    }),
    shareReplay(1)
  )

  meterChanged$ = this.userSelectionsForm.controls['meterId'].valueChanges.pipe(
    tap((meterId) => {
      const isMeterEmpty = meterId == null || meterId == undefined;
      const isSpecificMeter = meterId > 0;
      const setMeterToAll = () => { this.userSelectionsForm.get('meterId').patchValue(0, { emitEvent: false }); }
      const setSpecificPipeline = () => {
        const pipeForMeter = this.localRequiredData.meters.find(m => m.meterId == meterId).pipeId;
        this.userSelectionsForm.get('pipelineId').patchValue(pipeForMeter, { emitEvent: false });
        this.refreshContractForPipeline$.next(null);
        this.refreshCounterpartyForPipeline$.next(null);
      }

      if (isSpecificMeter)
        setSpecificPipeline();

      if (isMeterEmpty)
        setMeterToAll();

      this.refreshItems$.next(null);
    }),
    shareReplay(1)
  )

  counterpartyChanged$ = this.userSelectionsForm.controls['counterpartyId'].valueChanges.pipe(
    tap((counterpartyId) => {
      const isCounterpartyEmpty = counterpartyId == null || counterpartyId == undefined;
      const isPipelineSingle = this.userSelectionsForm.get('pipelineId').value > 0;
      const setCounterpartyToAll = () => { this.userSelectionsForm.get('counterpartyId').patchValue(0, { emitEvent: false }); }

      if (isCounterpartyEmpty && isPipelineSingle)
        setCounterpartyToAll();

      this.refreshItems$.next(null);
    }),
    shareReplay(1)
  )

  setTneMeterResult$ = this.setTneMeter$.pipe(
    filter(() => {
      const isBuy = this.actualTypeId === 1;
      return isBuy && this.clickedItem != null;
    }),
    switchMap(() => {
      const tneMeterItems = this.getTneMeterItems();
      return this.service.setTneMeter(this.clickedItem, tneMeterItems);
    }),
    tap((results: Item[]) => {
      this.refreshTneMeter(results);
    }),
    shareReplay(1),
    catchError(err => {
      return util.handleError(err, this.messageService)
    }), retry(3)
  );

  toggleTneMeterResult$ = this.toggleTneMeter$.pipe(
    filter(() => {
      const isBuy = this.actualTypeId === 1;
      return isBuy && this.clickedItem != null;
    }),
    switchMap(() => {
      const tneMeterItems = this.getTneMeterItems();
      return this.service.toggleTneMeter(this.clickedItem, tneMeterItems);
    }),
    tap((results: Item[]) => {
      this.refreshTneMeter(results);
    }),
    shareReplay(1),
    catchError(err => {
      return util.handleError(err, this.messageService)
    }), retry(3)
  );

  getTneMeterItems(): Item[] {
    const value = this.gridItems().filter(x => {
      const isMatchingNormalDeal =
        x.dealId == this.clickedItem?.dealId &&
        x.deliveryMeterId == this.clickedItem?.deliveryMeterId
        && x.lastTransferId == null;

      const isMatchingTransferDeal =
        x.lastTransferId != null &&
        x.lastTransferId == this.clickedItem?.lastTransferId;

      return isMatchingNormalDeal || isMatchingTransferDeal;
    });
    return value;
  }

  refreshTneMeter(results: Item[]): void {
    if (!results)
      return;

    results.forEach(result => {
      const gridItem: Item = this.gridItems()
        .find(x =>
          x.actualTypeId === result.actualTypeId &&
          x.supplyNomId === result.supplyNomId &&
          x.marketNomId === result.marketNomId &&
          x.lastTransferId === result.lastTransferId
        );

      if (gridItem) {
        gridItem.tneMeterId = result.tneMeterId;
        gridItem.tneMeterName = result.tneMeterName;
      }
    });

    this.save(util.SaveType.New);
  }

  title$ = of('Crude Actuals').pipe(
    tap((title) => util.trySetTitle(this.titleService, title))
  )

  filterActualTypes$ = new BehaviorSubject<string>(null)
  ActualTypes$ = this.filterActualTypes$.pipe(util.filterSpecials(this.gridLoading$, this.requiredData$, 'actualTypes', 'actualTypeName'));

  filterPipelines$ = new BehaviorSubject<string>(null)
  Pipelines$ = this.filterPipelines$.pipe(util.filterSpecials(this.gridLoading$, this.requiredData$, 'pipelines', 'pipelineName'));

  filterPipeContracts$ = new BehaviorSubject<string>(null)
  PipeContracts$ = this.filterPipeContracts$.pipe(util.filterSpecials(this.gridLoading$, this.subContractForPipeline$, null, 'pipeContractName'));

  filterMeters$ = new BehaviorSubject<string>(null)
  Meters$ = this.filterMeters$.pipe(util.filterSpecials(this.gridLoading$, this.subMeterForPipeline$, null, 'meterName'));

  filterCounterparties$ = new BehaviorSubject<string>(null)
  Counterparties$ = this.filterCounterparties$.pipe(util.filterSpecials(this.gridLoading$, this.subCounterpartyForPipeline$, null, 'counterpartyName'));

  openSupplyDealOrTransfer(dataItem: Item, dealNum?: string) {
    if (util.isNullOrWhitespace(dealNum))
      return;

    const isNormalDeal = dataItem.deliveryDealNum?.substring(0, 3) === 'CRO' && dataItem.receiptDealNum?.substring(0, 3) === 'CRO';
    const isTransferDeal = dataItem.ticketNum?.substring(0, 3) === 'TFR';

    if (isNormalDeal) {
      this.externalParams = { topless: 1, dealNum: dealNum, dealId: dataItem.dealId };
      this.dealsOpened$.next(true)
    }
    else if (isTransferDeal) {
      this.clickedTransferDealId = dataItem.transferDealId;
      this.clickedTransferDay = dataItem.day;
      this.transfersOpened$.next(true);
    }
  }

  openLastTransfer(dataItem: Item) {
    if (util.isNullOrWhitespace(dataItem.lastTransferId))
      return;

    this.clickedTransferDealId = dataItem.lastTransferId;
    this.clickedTransferDay = dataItem.day;
    this.transfersOpened$.next(true);
  }

  onTneMeterClicked(dataItem: Item): void {
    this.clickedItem = dataItem;
    this.clickedTransferDealId = dataItem?.lastTransferId;
    this.clickedTransferDay = dataItem?.day;

    if (!dataItem)
      return;

    if (dataItem.hasTransfers)
      this.openTneMeter();
    else
      this.toggleTneMeter$.next();
  }

  openTneMeter(): void {
    this.tneMeterOpened$.next(true);
  }

  closeTneMeter() {
    this.tneMeterOpened$.next(false);
    this.setTneMeter$.next();
  }

  getTitleSplit(text: string | HTMLElement): string[] {
    let title: string | null;

    if (typeof text === 'string') {
      title = text;
    } else if (text instanceof HTMLElement) {
      title = text?.getAttribute('data-title');
    } else {
      return [];
    }

    if (!title)
      return [];

    const textArray = title.split('\n');
    return textArray;
  }
}
