import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, HostListener, inject, ViewChild, ViewEncapsulation } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { MessageService } from '../_shared/services/message.service';
import * as util from '../_shared/utils/util';
import { BehaviorSubject, Observable, Subject, catchError, combineLatest, map, of, retry, shareReplay, switchMap, take, tap } from 'rxjs';
import { CustomFormBuilder } from '../_shared/services/custom-form-builder.service';
import { DialogService, DialogSettings } from '@progress/kendo-angular-dialog';
import { NotifyService } from '../_shared/services/notify.service';
import { State } from '@progress/kendo-data-query';
import { MeterService, Item, Detail, RequiredData, CommonDetail } from './meter.service'
import { saveAs } from '@progress/kendo-file-saver';
import dayjs from 'dayjs';
import { Router } from '@angular/router';
import { MeterCommonComponent } from '../metercommon/metercommon.component';
import { FAST_KENDO_COMMON, FAST_PAGE_COMMON } from '../app.config';
import { ComboBoxComponent } from '@progress/kendo-angular-dropdowns';
import { MeterProductComponent } from "../meter-product/meter-product.component";
import { SelectableSettings } from '@progress/kendo-angular-treeview';

@Component({
  selector: 'app-meter',
  templateUrl: './meter.component.html',
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [FAST_PAGE_COMMON, FAST_KENDO_COMMON, MeterCommonComponent, MeterProductComponent]
})
export class MeterComponent {
  @ViewChild("grid", { read: ElementRef }) kendoGridEl: ElementRef;
  @ViewChild('meterCommon') meterCommon?: MeterCommonComponent;
  @ViewChild("addProductBox") addProductBox: ComboBoxComponent;
  @HostListener('window:resize') onResize() {
    //this function is empty but for some reason it helps the window to resize faster
  };
  meterLoading$: Observable<boolean>;

  private service = inject(MeterService);
  private messageService = inject(MessageService);
  private titleService = inject(Title);
  private fb = inject(CustomFormBuilder);
  private ref = inject(ChangeDetectorRef);
  private dialogService = inject(DialogService);
  private notify = inject(NotifyService);
  private router = inject(Router);

  constructor() {
    this.detailInitialValues = this.detailForm.getRawValue() as Detail;

    this.meterLoading$ = combineLatest([this.commonDetailLoading$]).pipe(
      map(([commonLoading]) => {
        return commonLoading;
      }),
      shareReplay(1),
      catchError(err => {
        return util.handleError(err, this.messageService)
      }
      )
    )
  }

  meterId: number;
  productId: number;
  effectiveDate: Date;
  opened: boolean;
  util = util;

  detailForm = this.getDetailForm();
  asOfForm = this.getAsOfForm();
  gridScrollPosition: util.GridScrollPosition = { topPos: 0, leftPos: 0 };
  localRequiredData: RequiredData;
  hasModifyPermission = false;
  detailInitialValues: Detail;
  localDetail: Detail;
  mySelection: number[] = [];
  productSelection: number[] = [];
  gridLoading$ = new BehaviorSubject<boolean>(true)
  refreshItems$ = new BehaviorSubject<string>(null)
  exporting$ = new BehaviorSubject<boolean>(false)
  detailOpened$ = new BehaviorSubject<boolean>(false)
  refreshRequiredData$ = new BehaviorSubject(null)
  detailLoading$ = new BehaviorSubject<boolean>(false)
  refreshDetail$ = new BehaviorSubject<number>(null)
  save$ = new Subject<[util.SaveType, util.AfterCompletedAction]>()
  delete$ = new Subject()
  exportClicked$ = new Subject()
  commonDetailLoading$ = new BehaviorSubject<boolean>(true);

  filterProducts$ = new BehaviorSubject<string>(null);
  refreshMeterProducts$ = new Subject<number>();
  meterProductDetailOpened$ = new BehaviorSubject<boolean>(false);

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

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

  getDetailForm() {
    const fb = this.fb;
    const fg: util.FormModel<Detail> = fb.group({
      meterId: fb.ctr(0),
      commonDetail: fb.ctr(null),
    });
    return fg;
  }

  getAsOfForm() {
    const fb = this.fb;
    const asOfFg: util.FormModel<{ asOfDate: Date }> = fb.group({
      asOfDate: fb.ctr(dayjs().toDate())
    });

    return asOfFg;
  }


  asOfDateChanged$ = this.asOfForm.get('asOfDate').valueChanges.pipe(
    tap(() => {
      setTimeout(() => { //waits for form controls to update before calling refresh
        this.refreshItems$.next(null);
      });
    }),
    shareReplay(1),
    catchError(err => {
      return util.handleError(err, this.messageService)
    }), retry(1)
  )

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

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

  meterProducts$ = this.refreshMeterProducts$.pipe(
    tap(() => {
      this.detailLoading$.next(true);
    }),
    switchMap(() => {
      return this.service.getMeterProducts(this.meterId);
    }),
    tap(() => {
      this.detailLoading$.next(false);
      util.goToSavedGridScrollPos(this.kendoGridEl, this.gridScrollPosition);
    }),
    shareReplay(1),
    catchError(err => {
      return util.handleError(err, this.messageService);
    }), retry(10)
  )

  exportAction$ = this.exportClicked$.pipe(
    tap(() => {
      this.exporting$.next(true);
    }),
    switchMap(() => {
      return this.service.exportItems(this.state, this.asOfForm.get('asOfDate').value, 'Meter.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)
  )

  saveResult$ = this.save$.pipe(
    switchMap(([saveType, completedAction]) => {
      this.detailLoading$.next(true);
      this.detailForm.disable();
      const commonDetail = this.meterCommon.getCommonDetail() as CommonDetail;

      const commonDetailDateOnly = util.convertDatesToDateOnlyStrings(commonDetail, ['inactiveDate']);
      this.detailForm.patchValue({ commonDetail: commonDetailDateOnly, meterId: this.meterId });
      const itemToSave: Detail = this.detailForm.getRawValue();
      return combineLatest([this.service.saveDetail(itemToSave, saveType), of(completedAction)]);
    }),
    tap(([saveResult, action]) => {
      this.detailLoading$.next(false);
      this.notify.success('save successful');
      if (action === util.AfterCompletedAction.CloseDetail) {
        this.mySelection = [saveResult];
        this.closeDetail(false);
      }
      else {
        this.meterId = saveResult;
        this.detailForm.markAsPristine();
      }

    }),
    shareReplay(1),
    catchError(err => {
      this.detailLoading$.next(false);
      return util.handleError(err, this.messageService)
    }), retry(10)
  )

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

  products$ = this.filterProducts$.pipe(util.filterIdNames(this.detailLoading$, this.requiredData$, 'products'));

  refreshCommonLoading(loading: boolean) {
    this.commonDetailLoading$.next(loading);
  }

  checkFormValidity(): boolean {
    const commonFormValidity = this.meterCommon.isFormValid();
    if (commonFormValidity)
      return true;
    else
      return false;
  }

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

  onDetailClosing() {
    const isCommonFormDirty = this.meterCommon.isFormDirty();
    if (isCommonFormDirty) {
      const cancelCheckDialog: DialogSettings = ({
        title: 'Please confirm',
        content: 'You have unsaved changes. What would you like to do?',
        actions: [{ text: 'Save', cssClass: 'k-primary' }, { text: 'Don\'t Save' }, { text: 'Cancel' }],
        cssClass: 'utilPrompt'
      });
      this.dialogService.open(cancelCheckDialog).result.pipe(take(1)).subscribe(result => {
        if (util.getDialogAction(result) === util.dialogAction.Save) {
          this.save(util.SaveType.Normal, util.AfterCompletedAction.CloseDetail);
        }
        else if (util.getDialogAction(result) === util.dialogAction.DontSave) {
          this.closeDetail(false);
        }
        else if (util.getDialogAction(result) === util.dialogAction.Cancel) {
          return;
        }
      });
    }
    else {
      this.closeDetail(false);
    }
  }

  closeDetail = (isFromInterface: boolean) => {
    this.refreshItems$.next(null);
    this.detailOpened$.next(false);
    if (isFromInterface)
      util.goToSavedGridScrollPos(this.kendoGridEl, this.gridScrollPosition);
  }

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

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

  save = (saveType: util.SaveType, action: util.AfterCompletedAction) => {
    this.detailForm.markAllAsTouched();
    const formsValid = this.checkFormValidity();
    if (formsValid)
      this.save$.next([saveType, action]);
    else
      this.notify.error("validation failed");
  }

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

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

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

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

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

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

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

  getMaxWindowHeight() {
    return window.innerHeight;
  }

  getMaxWindowWidth() {
    return window.innerWidth;
  }

  getProductName(productId: number) {
    return this.localRequiredData.products.find(p => p.id === productId).name;
  }

  openProductDetail(productId: number, effectiveDate: Date) {
    this.productId = productId;
    this.effectiveDate = effectiveDate;
    this.meterProductDetailOpened$.next(true);
  }

  productDetailClosed() {
    this.productId = 0;
    this.effectiveDate = null;
    this.detailLoading$.next(true);
    this.meterProductDetailOpened$.next(false);
    this.refreshMeterProducts$.next(this.meterId);
  }

  addProduct(productId: number) {
    if (this.meterId == null || this.meterId == 0) {
      util.onDetailChanging(this.detailForm, this.dialogService, null, this.save, { extraActionParamValue: util.AfterCompletedAction.DoNothing });
    }
    else {
      this.openProductDetail(productId, this.effectiveDate);
    }
  }
}
