import { Component, inject, OnInit, ChangeDetectorRef} from '@angular/core';
import { HttpResponse } from '@angular/common/http';
import { ActivatedRoute } from '@angular/router';
import { Observable, forkJoin, tap, of } from 'rxjs';
import { finalize, map, debounceTime  } from 'rxjs/operators';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';

import SharedModule from 'app/shared/shared.module';
import { FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';

import { IVariable } from 'app/entities/variable/variable.model';
import { VariableService } from 'app/entities/variable/service/variable.service';
import { IPeriod } from 'app/entities/period/period.model';
import { PeriodStatus } from 'app/entities/enumerations/period-status.model';
import { PeriodService } from 'app/entities/period/service/period.service';
import { IUnit } from 'app/entities/unit/unit.model';
import { UnitService } from 'app/entities/unit/service/unit.service';
import { UnitTypeService } from 'app/entities/unit-type/service/unit-type.service';
import { VariableClassTypeService } from 'app/entities/variable-class-type/service/variable-class-type.service';
import { IVariableClass } from 'app/entities/variable-class/variable-class.model';
import { IOrganization } from 'app/entities/organization/organization.model';
import { OrganizationService } from 'app/entities/organization/service/organization.service';
import { IInputDataUpload } from 'app/entities/input-data-upload/input-data-upload.model';
import { InputDataUploadService } from 'app/entities/input-data-upload/service/input-data-upload.service';
import { DataSourceType } from 'app/entities/enumerations/data-source-type.model';
import { IUnitType } from 'app/entities/unit-type/unit-type.model';
import { InputDataService } from '../service/input-data.service';
import { IInputData, NewInputData } from '../input-data.model';
import { InputDataFormService, InputDataFormGroup } from './input-data-form.service';
import { OrganizationNature } from 'app/entities/enumerations/organization-nature.model';
/* import dayjs, { Dayjs } from 'dayjs'; */
import { dayjs } from 'app/config/dayjs';
import { AccountService } from 'app/core/auth/account.service';
import { UnitConverterService } from 'app/entities/unit-converter/service/unit-converter.service';
import { SecurityPermission } from 'app/security/security-permission.model';
import { Resources } from 'app/security/resources.model';
import { SecurityAction } from 'app/security/security-action.model';
import { AbstractResilientEntityComponent } from 'app/entities/resilient/resilient-entity.component';

@Component({
  standalone: true,
  selector: 'jhi-input-data-update',
  templateUrl: './input-data-update.component.html',
  styleUrl: './input-data-update.component.css',
  imports: [SharedModule, FormsModule, ReactiveFormsModule, MatProgressSpinnerModule],
})
export class InputDataUpdateComponent extends AbstractResilientEntityComponent<IInputData, NewInputData, InputDataService> implements OnInit {
  isSaving = false;
  inputData: IInputData | null = null;
  dataSourceTypeValues = Object.keys(DataSourceType);
  selectedVariableUnitType: IUnitType | null = null;
  currentUsername: string | null = null;
  variableValueHasError: boolean = false;

  /* Flags for spinner control */
  isVariableValueCalculating: boolean = false;
  isLoadingVariables: boolean = false;
  showSpinnerForVariableClasses: boolean = false;

  variablesSharedCollection: IVariable[] = [];
  variableClassesSharedCollection: IVariable[] = [];
  periodsSharedCollection: IPeriod[] = [];
  unitsSharedCollection: IUnit[] = [];
  organizationsSharedCollection: IOrganization[] = [];

  protected inputDataService = inject(InputDataService);
  protected inputDataFormService = inject(InputDataFormService);
  protected variableService = inject(VariableService);
  protected variableClassTypeService = inject(VariableClassTypeService);
  protected periodService = inject(PeriodService);
  protected unitService = inject(UnitService);
  protected unitTypeService = inject(UnitTypeService);
  protected organizationService = inject(OrganizationService);
  protected inputDataUploadService = inject(InputDataUploadService);
  protected activatedRoute = inject(ActivatedRoute);
  protected accountService = inject(AccountService);
  protected unitConverterService = inject(UnitConverterService);

  // eslint-disable-next-line @typescript-eslint/member-ordering
  editForm: InputDataFormGroup = this.inputDataFormService.createInputDataFormGroup();

  compareInputData = (o1: IInputData | null, o2: IInputData | null): boolean => this.inputDataService.compareEntity(o1, o2);

  compareVariable = (o1: IVariable | null, o2: IVariable | null): boolean => this.variableService.compareEntity(o1, o2);

  comparePeriod = (o1: IPeriod | null, o2: IPeriod | null): boolean => this.periodService.compareEntity(o1, o2);

  compareUnit = (o1: IUnit | null, o2: IUnit | null): boolean => this.unitService.compareEntity(o1, o2);

  compareOrganization = (o1: IOrganization | null, o2: IOrganization | null): boolean =>
    this.organizationService.compareEntity(o1, o2);

  compareInputDataUpload = (o1: IInputDataUpload | null, o2: IInputDataUpload | null): boolean =>
    this.inputDataUploadService.compareEntity(o1, o2);

  constructor(private cdr: ChangeDetectorRef, service: InputDataService) { super(service); }
  
  ngOnInit(): void {
    this.accountService.identity().subscribe(account => {
      this.currentUsername = account?.login ?? null;
    });

    this.activatedRoute.data.subscribe(({ inputData }) => {
      this.inputData = inputData;
      if (inputData) {
        // Editing an existing InputData
        forkJoin([
          this.loadUnitRelationshipOptions(inputData.variable.id),
          this.loadVariableClassRelationshipOptions(inputData.variable.id),
          this.loadVariableUnitValueType(inputData.variable)
        ]).subscribe(([units, variableClasses, valueType]) => {
          this.unitsSharedCollection = units;  
          this.variableClassesSharedCollection = variableClasses;
          this.selectedVariableUnitType = valueType;

          this.updateForm(inputData); // Now update the form
          this.showSpinnerForVariableClasses = false;
        });
      } else {
        // Creating an new InputData
        this.isLoadingVariables = true;

        this.editForm.get('sourceInputDataUpload')?.disable();
        this.editForm.get('sourceInputData')?.disable();
        this.editForm.patchValue({ sourceType: DataSourceType.MANUAL });
        this.editForm.patchValue({ dataDate: dayjs() }); // Here its editable, so, dayjs()
        this.editForm.patchValue({ creationDate: dayjs().format('ddd, DD MMM YYYY HH:mm:ss [GMT]') });  //format('YYYY-MM-DD') toString, because the form is read only
        this.editForm.patchValue({ dataUser: this.currentUsername }); // Suggestion, the user can change the text
        this.editForm.patchValue({ creationUsername: this.currentUsername });
        this.editForm.patchValue({ changeUsername: this.currentUsername });

        // Load lists, only in create mode
        this.loadPeriods();

        // Listen to changes to the variable property
        this.editForm.get('variable')?.valueChanges.subscribe((variable) => {
          this.editForm.get('sourceUnit')?.setValue(null);
          this.editForm.get('unit')?.setValue(null);
          
          if (variable) {
            // Load and set BaseUnit. (Also, sugest baseUnit for the sourceUnit )
            this.variableService.queryForBaseUnit(variable.id)
            .pipe(map((res: HttpResponse<IUnit>) => res.body as IUnit))
            .subscribe((unit: IUnit) => {
              this.editForm.get('unit')?.setValue(unit);
              this.editForm.get('sourceUnit')?.setValue(unit);
            });

            // Show/hide value or text properties
            this.loadVariableUnitValueType(variable).subscribe((valueType) => {
              this.selectedVariableUnitType = valueType;

              // Validators
              const sourceValueControl = this.editForm.get('sourceValue');
              const imputedValueControl = this.editForm.get('imputedValue');
              const variableValueControl = this.editForm.get('variableValue');
              const stringValueControl = this.editForm.get('stringValue');
              sourceValueControl?.clearValidators();
              imputedValueControl?.clearValidators();
              variableValueControl?.clearValidators();
              stringValueControl?.clearValidators();

              if (this.selectedVariableUnitType) {
                switch (this.selectedVariableUnitType.valueType) {
                  case 'DECIMAL':
                    sourceValueControl?.setValidators([Validators.required]); 
                    imputedValueControl?.setValidators([Validators.required]); 
                    variableValueControl?.setValidators([Validators.required]); 
                    break;
                  case 'STRING':
                    stringValueControl?.setValidators([Validators.required]); 
                    break;
                  default:
                    console.log('Unknown fruit.');
                }
              }
                
              // Update the validity of the control's
              sourceValueControl?.updateValueAndValidity();
              imputedValueControl?.updateValueAndValidity();
              variableValueControl?.updateValueAndValidity();
              stringValueControl?.updateValueAndValidity();
            });

            this.loadVariableClassRelationshipOptions(variable.id).subscribe((variableClasses) => {
              this.variableClassesSharedCollection = variableClasses;
              this.showSpinnerForVariableClasses = false;
            });
          } else {
            this.loadVariableClassRelationshipOptions(null).subscribe((variableClasses) => {
              this.variableClassesSharedCollection = variableClasses;
              this.showSpinnerForVariableClasses = false;
            });
          }

          this.loadUnitsForVariable(variable).subscribe((units: IUnit[] | null) => {
            if (units) {
              this.unitsSharedCollection = units;

              if (units.length == 1) {
                this.editForm.patchValue({ sourceUnit: this.unitsSharedCollection[0] });
              }
            }
          });
        });

        // Listen to changes to the sourceValue property
        this.editForm.get('sourceValue')?.valueChanges
          .pipe( debounceTime(400) ) // waits 400ms after user stops typing. This prevents calling REST onType
          .subscribe((value) => { this.calculateVariableValue(); });
      }

      // Listen to changes to the sourceUnit property
      this.editForm.get('sourceUnit')?.valueChanges
      .subscribe((value) => { this.calculateVariableValue(); });
      
      this.loadRelationshipsOptions();
    });
  }

  previousState(): void {
    window.history.back();
  }

  onVariableClassSelectionChange(event: Event): void {
    const variableClassCodeSelectedElement = document.querySelector('#field_variableClassCode') as HTMLSelectElement;
    
    const selectElement = event.target as HTMLSelectElement;
    const selectedText = selectElement.options[selectElement.selectedIndex].text;
    
    this.editForm.patchValue({variableClassName: selectedText});
  }
  
  save(): void {
    this.isSaving = true;
    const inputData = this.inputDataFormService.getInputData(this.editForm);
    if (inputData.id !== null) {
      this.subscribeToSaveResponse(this.inputDataService.update(inputData));
    } else {
      this.subscribeToSaveResponse(this.inputDataService.create(inputData));
    }
  }

  private calculateVariableValue(): void {
    // To calculate, must have:
    //  - sourceUnit NOT null
    //  - sourceUnit.unitType.valueType == DECIMAL
    //  - sourceValue NOT null
    const sourceUnit: IUnit = this.editForm.get('sourceUnit')?.value!;
    const unit: IUnit = this.editForm.get('unit')?.value!;
    const variable: IVariable = this.editForm.get('variable')?.value!;
    const sourceValue = this.editForm.get('sourceValue')?.value;
    
    this.variableValueHasError = false;
    this.isVariableValueCalculating = true;

    if (sourceUnit && sourceValue && unit && variable) {
      // Set values
      if (sourceUnit.unitType?.id == unit.unitType?.id) {
        // Doesn't need convertion
        this.editForm.get('imputedValue')?.setValue(sourceValue);
        this.editForm.get('variableValue')?.setValue(sourceValue);

        this.isVariableValueCalculating = false;
      } else {
        // Needs convertion
        const period: IPeriod = this.editForm.get('period')?.value!;
        const variableClassCode = this.editForm.get('variableClassCode')?.value;

        this.unitConverterService.convertValue(period.id, variable.id, variableClassCode ?? null, sourceUnit.id, sourceValue)
          .subscribe({
            next : (convertedValue) => {
              const myConvertedValue = convertedValue.body;

              this.editForm.get('imputedValue')?.setValue(myConvertedValue);
              this.editForm.get('variableValue')?.setValue(myConvertedValue);

              this.isVariableValueCalculating = false;
            },
            error: (err) => {
              // Optionally show a message or set fallback value
              this.editForm.get('imputedValue')?.setValue(null);
              this.editForm.get('variableValue')?.setValue(null);

              this.variableValueHasError = true;
              this.isVariableValueCalculating = false;
              /*
              const variableValueField = this.editForm.get('variableValue');
              variableValueField?.setErrors({ ...(variableValueField.errors || {}), customError: 'Unable to convert the value between unit\'s' });
              variableValueField?.updateValueAndValidity();
              */
            }
          });
      }
      
    } else {
      // Clear values
      this.editForm.get('imputedValue')?.setValue(null);
      this.editForm.get('variableValue')?.setValue(null);

      this.isVariableValueCalculating = false;
    }
  }

  protected subscribeToSaveResponse(result: Observable<HttpResponse<IInputData>>): void {
    result.pipe(finalize(() => this.onSaveFinalize())).subscribe({
      next: () => this.onSaveSuccess(),
      error: () => this.onSaveError(),
    });
  }

  protected onSaveSuccess(): void {
    this.previousState();
  }

  protected onSaveError(): void {
    // Api for inheritance.
  }

  protected onSaveFinalize(): void {
    this.isSaving = false;
  }

  protected updateForm(inputData: IInputData): void {
    this.inputData = inputData;
    this.inputDataFormService.resetForm(this.editForm, inputData);

    this.variablesSharedCollection = this.variableService.addEntityToCollectionIfMissing<IVariable>(
      this.variablesSharedCollection,
      inputData.variable,
    );
    this.periodsSharedCollection = this.periodService.addEntityToCollectionIfMissing<IPeriod>(
      this.periodsSharedCollection,
      inputData.period,
    );
    this.unitsSharedCollection = this.unitService.addEntityToCollectionIfMissing<IUnit>(
      this.unitsSharedCollection,
      inputData.sourceUnit,
      inputData.unit,
    );
    this.organizationsSharedCollection = this.organizationService.addEntityToCollectionIfMissing<IOrganization>(
      this.organizationsSharedCollection,
      inputData.owner,
    );
    
    // Update the variableClassName property by simulating a the call to change event
    // This is needed because the user might have changed the name of the class name in the table, and now wants to update-it
    this.cdr.detectChanges();       // Forces HTML to render and update SelectList with the variableClass options.
    const variableClassCodeSelectedElement = document.querySelector('#field_variableClassCode') as HTMLSelectElement; //Get select option
    const variableClassNameText = variableClassCodeSelectedElement ? variableClassCodeSelectedElement.options[variableClassCodeSelectedElement.selectedIndex].text : '';
    this.editForm.patchValue({variableClassName: variableClassNameText});
  }

  protected loadUnitsForVariable(variable: IVariable|null|undefined): Observable<IUnit[] | null> {
    if (variable) {
      return this.unitService
              .queryForVariable(variable.id)
              .pipe(map((res: HttpResponse<IUnit[]>) => res.body ?? []));
    } else {
      return of(null).pipe(
        tap(() => {
          this.unitsSharedCollection = [];
        })
      );
    }
  }

  protected loadUnitRelationshipOptions(variableId: number): Observable<IUnit[]> {
    // Load units allowed fot the selected Variable
    return this.unitService
      .queryForVariable(variableId)
      .pipe(
        map((res: HttpResponse<IUnit[]>) => res.body ?? []),
        map((units: IUnit[]) =>
          this.unitService.addEntityToCollectionIfMissing<IUnit>(units, this.inputData?.sourceUnit, this.inputData?.unit),
        ),
      );
  }
  
  protected loadVariableUnitValueType(variable: IVariable): Observable<IUnitType | null>{
    // Load UnitValueType for the selected vaariable
    if (variable.baseUnit?.unitType) {
      return this.unitTypeService
        .find(variable.baseUnit.unitType.id)
        .pipe(
          map((res: HttpResponse<IUnitType>) => res.body ?? null)
        );
    } else {
      // Handle the case where no request is needed by returning an Observable
      return of(null).pipe(
        tap(() => {
          // No variable. Clear dependante internal values.
          this.selectedVariableUnitType = null;
          this.variableClassesSharedCollection = [];
          this.unitsSharedCollection = [];
          
          // Clear form dependante values
          this.editForm.patchValue({
            variableClassCode: null,
            variableClassName: null,
            sourceUnit: null
          });
        })
      );
    }
  }
  
  protected loadVariableClassRelationshipOptions(variableId: number | null): Observable<IUnit[]> {
    this.showSpinnerForVariableClasses = true;

    // Load units allowed for the selected Variable.
    if (variableId) {
      return this.variableClassTypeService
      .queryForVariable(variableId)
      .pipe(map((res: HttpResponse<IVariableClass[]>) => res.body ?? []));
    } else {
      return of([]);
    }

  }
  
  protected loadPeriods() {
    this.periodService
      .queryByState(PeriodStatus.OPENED)
      .pipe(map((res: HttpResponse<IPeriod[]>) => res.body ?? []))
      .pipe(map((periods: IPeriod[]) => this.periodService.addEntityToCollectionIfMissing<IPeriod>(periods, this.inputData?.period)))
      .subscribe((periods: IPeriod[]) => {
        this.periodsSharedCollection = periods;

        if (periods.length == 1) {
          this.editForm.patchValue({ period: this.periodsSharedCollection[0] });
        }
      });
  }

  protected loadRelationshipsOptions(): void {
    this.variableService
      .queryForManualInput()
      .pipe(map((res: HttpResponse<IVariable[]>) => res.body ?? []))
      .pipe(
        map((variables: IVariable[]) =>
          this.variableService.addEntityToCollectionIfMissing<IVariable>(variables, this.inputData?.variable),
        ),
      )
      .subscribe((variables: IVariable[]) => { 
        this.variablesSharedCollection = variables;
        this.isLoadingVariables = false;
      });

    this.queryOrganization()
      .pipe(map((res: HttpResponse<IOrganization[]>) => res.body ?? []))
      .pipe(
        map((organizations: IOrganization[]) =>
          this.organizationService.addEntityToCollectionIfMissing<IOrganization>(organizations, this.inputData?.owner),
        ),
      )
      .subscribe((organizations: IOrganization[]) => (this.organizationsSharedCollection = organizations));
  }

  private queryOrganization():  Observable<HttpResponse<IOrganization[]>> {
    // Distinguish the organization collection, if user has access to ALL or only HIERARCHY
    let  permission: SecurityPermission = SecurityPermission.NONE;
    if (this.inputData?.id) {
      permission = this.accountService.getPermission(Resources.INPUT_DATA, SecurityAction.UPDATE);
    } else {
      permission = this.accountService.getPermission(Resources.INPUT_DATA, SecurityAction.CREATE);
    }
    
    if (permission === SecurityPermission.ALL) {
      // Access to all
      return this.organizationService.queryByNatureForInput(OrganizationNature.ORGANIZATION)
    } if (permission === SecurityPermission.HIERARCHY) {
      // Only user hierarchy
      return this.organizationService.queryByNatureAndHierarchyForInput(OrganizationNature.ORGANIZATION);
    } else {
      // NOTHING
      return of(new HttpResponse({ body: [], status: 200 }));
    }
  }
}
