import * as keycode from '@angular/cdk/keycodes';
import { inject } from '@angular/core';
import { NgForm } from '@angular/forms';
import { BehaviorSubject, forkJoin, Subject, Subscription } from 'rxjs';
import { cnst_CANCEL_ERROR, EN_EntityBeforeSaveCallBackResults, EN_EntityEditingStates } from '../../consts';
import { IMessageBoxData } from '../../services/globals';
import { AppLogsService } from '../../services/applogs.service';
import { RepositoryService } from '../../services/http/repository.service';
import { HenselTranslateService } from '../../services/localization/hensel-translate.service';
import { getErrorMessageAsText, now } from '../../utils';
import { ResultSubject } from '../resultsubject';
import { BaseEntity, BaseEntityCallBack, ErrorCallBack } from './baseentity';
import { BaseEntityDetail, BaseEntityDetailList, mdLOAD, mdSAVE } from './baseentitydetaillist';
import { BaseEntityListWrapper } from './baseentitylist.wrapper';
import { MatDialogRef } from '../../components/angular-material-index';


export enum EN_SynchronizeListAfter {
  Load = 0,
  New = 1,
  Save = 2
}


export class BaseEntityWrapper<T extends BaseEntity> {
  public static EN_ViewMode = EN_EntityEditingStates.View;
  public static EN_NewMode = EN_EntityEditingStates.New;
  public static EN_EditMode = EN_EntityEditingStates.Edit;
  public static EN_KeyEditMode = EN_EntityEditingStates.EditKey;
  public static EN_Idle = EN_EntityEditingStates.Idle;
  public entityList: BaseEntityListWrapper<BaseEntity>;
  public delayEntityChange = 500; //delay to set changed flag
  public delaySetEditModeCallback = 500; //delay to call callback after edit mode was changed
  public delayToReloadedEntity = 2000; //reload entity only after delay since it was loaded last time
  public loadedDataExpiresInMs: number = 0; //reload entity before edit if it was loaded longer as loadedDataExpiresInMs, if loadedDataExpiresInMs = 0 - no reload before edit

  public syncEntityListWhenSave: boolean = true;
  public syncEntityListWhenLoad: boolean = true;
  private _lastChangedTime = 0; //to avoid repeateadly changing of changed during loading/assigning
  private _detailForm: NgForm;
  protected _entity: T;


  public errorCallBack: ErrorCallBack;
  public loadedCallBack: BaseEntityCallBack;
  public completeCallBack: () => void;
  public beforeNewCallBack: BaseEntityCallBack; //callback to be called before insert
  public beforeSaveCallBack: (entity: BaseEntity) => Promise<EN_EntityBeforeSaveCallBackResults>; //callback to be called before save, returns 0-all ok, continue saving, 1-all ok, break further saving, 2 - error, ignore coninue
  public beforeDeleteCallBack: (entity: BaseEntity) => Promise<EN_EntityBeforeSaveCallBackResults>; //callback to be called before save, returns 0-all ok, continue saving, 1-all ok, break further saving, 2 - error, ignore coninue
  public isEntityChangedCallBack: (entity: BaseEntity) => boolean;
  public isEntityValidCallBack: (entity: BaseEntity) => boolean;

  public detailFormHTMLId: string;
  public entityLoaded$: Subject<void> = new Subject();

  public entityIsLoading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
  public onCheckFormValidInput: (form: NgForm) => boolean;
  public get entityIsLoading(): boolean {
    return this.entityIsLoading$.value;
  }
  public set entityIsLoading(value: boolean) {
    const wasLoading = this.entityIsLoading;
    if (!value && wasLoading || value && !wasLoading) this.entityIsLoading$.next(value);
    this.appLogsService.dlog('--- Loading', this.entity.typeName + ': ' + this.entity.entityId + ' == ' + value);
    if (!value && wasLoading) this.entityLoaded$.next();
  }

  public indexInList4New: number = -1; //last

  private _keyDownListenerStopped: boolean = false;
  public get keyDownListenerStopped(): boolean {
    return this._keyDownListenerStopped ||
      this.detailItems.items.some((item: BaseEntityDetail) => item.baseEntityList ? item.baseEntityList.focusedEntityShadowed?.keyDownListenerStopped : item.baseEntityWrapper.keyDownListenerStopped);
  }
  public set keyDownListenerStopped(value: boolean) {
    this._keyDownListenerStopped = value;
  }

  public hierarchyLevel: number = 0;
  public confirmationsOn: boolean = true;
  public dontShowErrors: boolean = false;

  private _editModeDefault: EN_EntityEditingStates = BaseEntityWrapper.EN_ViewMode;
  public get editModeDefault(): EN_EntityEditingStates {
    return this._editModeDefault;
  }
  public set editModeDefault(value: EN_EntityEditingStates) {
    if (value !== null && value !== undefined) {
      this._editModeDefault = value;
      this.resetEditMode();
    }
  }

  private _lastLoadedEntityId: number = 0;
  private _editMode: EN_EntityEditingStates = this.editModeDefault;
  public get editMode(): EN_EntityEditingStates {
    return this._editMode;
  }
  public set editMode(newState: EN_EntityEditingStates) {
    if (this.editMode === newState) return;
    setTimeout(() => {
      this.editModeDirectly = newState;
      this.entityWasChanged = false; // to avoid changed state imedeately after going into edit mode (eable/disable buttons trigger form change event too)
      if (this.inNotViewMode) this.setFocus4Form(!this.detailFormHTMLId ? 'form_' + this.entity.typeName : this.detailFormHTMLId);
    }, 0); //timeout to avoid "change after checked" warning
  }

  public set editModeDirectly(newState: EN_EntityEditingStates) {
    this._editMode = newState;
    this.appLogsService.dlog('--- Editmode', this.entity.typeName + ': ' + this.entityId + ' == ' + newState);
  }

  get entitytitle(): string {
    const title = this.entity?.entitytitle;
    return this.translate.translateString(title);
  }

  public canNew: boolean = true;
  public canEdit: boolean = true;
  public canCancel: boolean = true;
  public canDelete: boolean = true;
  public dontLoadEntity: boolean = false;
  public dontCheckSaveButton: boolean = false;
  public dontUpdateEntityDirectly: boolean = false;
  public dontInsertEntityDirectly: boolean = false;

  public detailItems: BaseEntityDetailList = new BaseEntityDetailList();

  private _wasChanged: boolean = false;
  private delayedEditsubscription: Subscription;
  protected translate: HenselTranslateService;
  private loginSubscriptionSave: Subscription;
  private loginSubscriptionLoad: Subscription;

  protected appLogsService: AppLogsService;
  protected repositoryService: RepositoryService;

  constructor(
    protected TEntity: new (source?: BaseEntity) => T,
    defaultEditMode?: EN_EntityEditingStates,
    appLogsService?: AppLogsService,
    repositoryService?: RepositoryService,
  ) //
  {
    this.appLogsService = appLogsService ?? inject(AppLogsService);
    this.repositoryService = repositoryService ?? inject(RepositoryService);
    this.translate = this.appLogsService.translate;

    if (defaultEditMode !== null && defaultEditMode !== undefined) {
      this._editModeDefault = defaultEditMode;
      this._editMode = defaultEditMode;
    }

    setTimeout(() => { this.entityWasChanged = false; }, 0); //to avoid the changed was set during the initial binding
    this._entity = new TEntity();
  }

  public get isNew(): boolean {
    return !this.entity || (
      this.entity.entityId === 0 /* because of if this.dontInsertEntityDirectly it can be <0*/
      || this.entity.insertWithIds() && this.entity.entityId < 0 /* or if for insert Id is set explicitely */
    );
  }

  public get inViewMode(): boolean { return this.editModeDefault !== BaseEntityWrapper.EN_ViewMode && !this.inNewMode || this.editMode === BaseEntityWrapper.EN_ViewMode; }
  public get inNotViewMode(): boolean { return this.editMode === BaseEntityWrapper.EN_EditMode || this.editMode === BaseEntityWrapper.EN_KeyEditMode || this.editMode === BaseEntityWrapper.EN_NewMode; }
  public get inNewKeyEditMode(): boolean { return this.editMode === BaseEntityWrapper.EN_KeyEditMode || this.editMode === BaseEntityWrapper.EN_NewMode; }
  public get inNewEditMode(): boolean { return this.editMode === BaseEntityWrapper.EN_EditMode || this.editMode === BaseEntityWrapper.EN_NewMode; }
  public get inEditMode(): boolean { return this.editMode === BaseEntityWrapper.EN_EditMode; }
  public get inNewMode(): boolean { return this.editMode === BaseEntityWrapper.EN_NewMode; }
  public get inKeyEditMode(): boolean { return this.editMode === BaseEntityWrapper.EN_KeyEditMode; }
  public onFormChange: (val: any) => boolean;

  public resetEditMode() {
    if (this.hierarchyLevel === 0 && this.isNew && this.editModeDefault !== EN_EntityEditingStates.View) return; //this logic is used only for entity wrapper 1st level
    this.editMode = this.editModeDefault;
  }

  public set entityWasLoaded(finished: boolean) {
    this.entityIsLoading = !finished;
    if (finished) this.entityWasChanged = false;
  }

  // we set false imedeatelely, but true only after entityChangeDelay=def 500 ms were gone, since the last call to set
  public set entityWasChanged(changed: boolean) {
    if (this.entity) {
      this.appLogsService.dlog('--- ' + this.entity.typeName + ' CHANGED: ', changed, ' after ', now() - this._lastChangedTime);
      if (!changed || (now() - this._lastChangedTime > this.delayEntityChange)) {
        setTimeout(() => this.entity.entityChanged = changed); //to avoid change after check error, because is used in interface onSaveEnabled
        this._wasChanged = changed; //to return properly value from this.changed()
      }
      this._lastChangedTime = now();
    }
  }

  public get entityWasChanged() {
    return this.entity?.entityChanged || this.detailItems.changed()
      || (!!this.isEntityChangedCallBack && this.isEntityChangedCallBack(this.entity));
  }

  public get isValidInput() {
    return this.entity?.isValidInput() && this.detailItems.isValidInput()
      && (!this.isEntityValidCallBack || this.isEntityValidCallBack(this.entity));
  }


  public get detailForm(): NgForm {
    return this._detailForm;
  }

  public set detailForm(value: NgForm) {
    this._detailForm = value;
    //connect input fields of the detail form with entity change method automatically
    if (!this._detailForm) return;
    this.detailForm.valueChanges.subscribe((val: any) => this.doFormChange(val));
    this.updateRequired();
  }


  doFormChange = (val: any) => {
    if (!this.onFormChange || !this.onFormChange(val)) {
      this.changed(val);
    }
  }


  public updateRequired() {
    if (!this.detailForm) return;
    // to show markup (read) for required fields imediately after a form was initialised,
    // but not only after the field was focused
    setTimeout(() => { if (this.detailForm?.control) this.detailForm.control.markAllAsTouched(); });
  }

  get entity() { return this._entity; }


  //just connects UI wrapper with a given enity object
  set entity(ent: T | null) {
    this.entityWasLoaded = false;
    this._entity = ent;
    this.entityWasLoaded = true;
  }

  //load an entity object on its Id and connects UI wrapper with it
  set entityId(id: number) {
    // tslint:disable-next-line: curly
    if ((!this.entity) || (!this.entity.isRealObject())) {
      this._entity = new this.TEntity();
    }

    this.load(id);
  }

  get entityId() { return this.entity ? this.entity.entityId : null; }


  private unsubscribe(subscr: Subscription) {
    subscr?.unsubscribe();
    subscr = null;
  }


  public load(entityId: number, _loadedCallBack?: BaseEntityCallBack, _errorCallBack?: ErrorCallBack, _completeCallBack?: () => void) {
    this.entity.entityId = entityId;

    if (!this.loginSubscriptionLoad && this.hierarchyLevel === 0) {
      this.loginSubscriptionLoad = this.repositoryService.authService.loginAction$.subscribe((resultObject) => {
        if (resultObject.result) { // try one time more
          this.load(entityId, _loadedCallBack, _errorCallBack, _completeCallBack);
        } else this.unsubscribe(this.loginSubscriptionLoad);

      });
    } else {
      if (this.loginSubscriptionLoad) this.unsubscribe(this.loginSubscriptionLoad); // try only one time more
    }

    const loadedDetails$: Subject<void>[] = [new Subject()];
    // if (!_loadedCallBack) _loadedCallBack = this.loadedCallBack;
    // if (!_errorCallBack) _errorCallBack = this.errorCallBack;
    // if (!_completeCallBack) _completeCallBack = this.completeCallBack;

    this.detailItems.prepareObservables(loadedDetails$, mdLOAD);

    forkJoin(loadedDetails$).subscribe(() => {

      this.entityWasLoaded = true;
      this.resetEditMode();
      if (this.hierarchyLevel === 0) {
        this.appLogsService.dlog('------- master loaded <' + this.entity.typeName + ' ' + this.entity.entityId + '>');
        this.unsubscribe(this.loginSubscriptionLoad);
      }
      if (_loadedCallBack) _loadedCallBack(this.entity);
      if (this.loadedCallBack) this.loadedCallBack(this.entity);
      if (this.entityId) { // do check because for empty list focusedEntityShadowed.entityId is set to 0, to clear details
        this.entityList?.updateListItem(this.entity); //to update entity if it was chaged in loadedCallBack
      }
    }, (error: ResultSubject) => {
      this.cancelDelayedEditsubscription();
      this.entityIsLoading = false;
      if (this.hierarchyLevel === 0) this.appLogsService.error('---!!! master error <' + this.entity.typeName + ' ' + this.entity.entityId + '>:', { error });
      const errorMsg = getErrorMessageAsText(error);
      if (errorMsg !== cnst_CANCEL_ERROR) {
        if (!this.dontShowErrors) this.errorMessageBox(this.translate.instant('core.error.loading', { value: this.entitytitle }), errorMsg);
        if (_errorCallBack) _errorCallBack(error);
        if (this.errorCallBack) this.errorCallBack(error);
        this.unsubscribe(this.loginSubscriptionLoad); //relogin is not executed - don't need subscription
      }
      if (_completeCallBack) _completeCallBack();
      if (this.completeCallBack) this.completeCallBack();
    }, () => {
      this.entityWasLoaded = true;  //important for new mode, because callback loaded in this case is not called

      if (this.hierarchyLevel === 0) this.appLogsService.dlog('------- master loaded completed <' + this.entity.typeName + ' ' + this.entity.entityId + '>');
      if (_completeCallBack) _completeCallBack();
      if (this.completeCallBack) this.completeCallBack();
    });

    //load entity itself
    this.entityWasLoaded = false;
    if (this.dontLoadEntity && this.entityList) {
      if (this.entityList.items.length > 0 && this.entityId) {//do check because for empty list focusedEntityShadowed.entityId is set to 0, to clear details
        this.entity.assign(this.entityList.getItemById(this.entity.entityId));

        loadedDetails$[0].next();
        //load detail lists
        this.detailItems.load(this, loadedDetails$, 1);
        loadedDetails$[0].complete();
      } else {
        this.clear();
        loadedDetails$[0].complete();
      }
    } else {
      this.entity.load(this.repositoryService, (entity) => {
        this.synchroniseEntityList(EN_SynchronizeListAfter.Load);
        loadedDetails$[0].next();

        //load detail lists, must be in call back
        //this.detailItems.load(this, loadedDetails$, 1);
      }, (error: any) => {
        if (this.hierarchyLevel > 0) {
          loadedDetails$[0].next();
          loadedDetails$[0].complete(); //w/o complete is passed no next
          //error in details does not mean error in main
        } else loadedDetails$[0].error(error);
        // this.entityWasLoaded = false;
      }, () => {
        //load detail lists, must be in call back
        this.detailItems.load(this, loadedDetails$, 1);
        loadedDetails$[0].complete();
      }
      );
    }
  }


  public changed(event?: any): boolean {
    this.entityWasChanged = true;
    return this._wasChanged; //can be the case that set it to true has no affect, because of delay after it was set to false
  }

  isFormValidInput() {
    return ((this.onCheckFormValidInput && this.onCheckFormValidInput(this.detailForm)) || (!this.detailForm || (this.detailForm && !this.detailForm.invalid
      /* valid was changed against !invalid, because valid = status==VALID, but status for disabled is DISABLED, i.e. disabled -> !valid*/
    ))) && this.isValidInput;
  }

  public enabledSaveButton() {
    return this.isFormValidInput() && this.inNotViewMode && (this.entityWasChanged || this.dontCheckSaveButton);
  }
  public enabledDeleteButton() { return this.canDelete && (!this.isNew || this.dontUpdateEntityDirectly) && !this.entityIsLoading && this.inViewMode; }
  public enabledNewButton() { return this.canNew && !this.entityIsLoading && this.inViewMode /*&& !this.isNew && !this.entityWasChanged */; }
  public enabledEditButton() { return this.canEdit && (!this.isNew || this.dontInsertEntityDirectly) && !this.entityIsLoading && this.inViewMode; }
  public enabledCancelButton() {
    return this.canCancel
      && (
        this.editModeDefault === BaseEntityWrapper.EN_ViewMode && this.inNotViewMode ||
        this.editModeDefault !== BaseEntityWrapper.EN_ViewMode && (
          this.inEditMode && this.entityWasChanged ||
          this.inNewMode && (!this.entityList || this.entityList.focusedItem || //if there is a binded list with the selected item, then load it with cancel
            this.entityWasChanged) //or clear if there was changed something
        )
      )
      ///*(this.isNew || this.entityWasChanged || this.editModeDefault === BaseEntityWrapper.EN_ViewMode) && */ this.inNotViewMode
      && !this.entityIsLoading;
  }
  public enabledCopyButton() { return this.canNew && (!this.isNew || this.dontInsertEntityDirectly) && !this.entityIsLoading && this.inViewMode; }


  public async saveExcludingFields(excludeFields: string, _loadedCallBack?: BaseEntityCallBack, _errorCallBack?: ErrorCallBack, _completeCallBack?: () => void, useSuffixInsteadEntityId?: string) {
    this.entity.excludeFieldFromUpdate(excludeFields);
    this.save(_loadedCallBack, _errorCallBack
      , () => {
        this.entity.includeFieldForUpdate(excludeFields);
        if (_completeCallBack) _completeCallBack();
      }
      , useSuffixInsteadEntityId
    );
  }


  public async saveAsync(): Promise<boolean> {
    return new Promise<boolean>((resolve, reject) => this.save(null, () => resolve(false), () => resolve(true)));
  }


  public async saveAnywayAsync(): Promise<boolean> {
    return new Promise<boolean>((resolve, reject) => this.saveAnyway(null, () => resolve(false), () => resolve(true)));
  }


  public saveAnyway(_loadedCallBack?: BaseEntityCallBack, _errorCallBack?: ErrorCallBack, _completeCallBack?: () => void, useSuffixInsteadEntityId?: string) {
    if (!this.entity) return;
    this.save(_loadedCallBack, _errorCallBack, _completeCallBack, useSuffixInsteadEntityId, true);
  }


  public async save(_loadedCallBack?: BaseEntityCallBack, _errorCallBack?: ErrorCallBack, _completeCallBack?: () => void, useSuffixInsteadEntityId?: string, dontCheckSaveButton: boolean = false) {

    if (this.entity && (dontCheckSaveButton || this.enabledSaveButton())) {
      const savedDetails$: Subject<void>[] = [new Subject()];
      const _isNew = this.isNew;

      let resBeforeSaveCallBack: EN_EntityBeforeSaveCallBackResults = EN_EntityBeforeSaveCallBackResults.Ok_Continue;
      // tslint:disable-next-line: no-bitwise
      const doBreak: boolean = this.beforeSaveCallBack && ((resBeforeSaveCallBack = await this.beforeSaveCallBack(this.entity)) & EN_EntityBeforeSaveCallBackResults.Ok_Break) !== 0;
      if (!doBreak) { //saves main base entity only after all details entities/entity lists are successfuly saved
        this.detailItems.prepareObservables4SaveAutonome(savedDetails$, mdSAVE, () => this.saveEntity(savedDetails$[0], useSuffixInsteadEntityId));
      }


      forkJoin(savedDetails$).subscribe(
        () => {
          this.unsubscribe(this.loginSubscriptionSave);
          this.resetEditMode();
          if (!this.synchroniseEntityList(_isNew ? EN_SynchronizeListAfter.New : EN_SynchronizeListAfter.Save)) this._cancel(); //don't change order here!!!
          if (_loadedCallBack) _loadedCallBack(this.entity);
          this.appLogsService.dlog('------- master saved <' + this.entity.typeName + ' ' + this.entity.entityId + '>');
          this.entityList?.updateListItem(this.entity); //to update entity if it was chaged in loadedCallBack
          this.entityWasChanged = false;
        }, (error: ResultSubject) => {
          this.entityWasChanged = true;
          this.appLogsService.error('---!!! master error <' + this.entity.typeName + '>:', { error });
          const errorMsg = getErrorMessageAsText(error);
          if (errorMsg !== cnst_CANCEL_ERROR) {
            if (!this.dontShowErrors) this.errorMessageBox(this.translate.instant('core.error.saving', { value: this.entitytitle }), errorMsg);
            if (_errorCallBack) _errorCallBack(error);
            this.unsubscribe(this.loginSubscriptionSave); //relogin is not executed - don't need subscription
          }
          if (_completeCallBack) _completeCallBack();
        }, () => {
          this.appLogsService.dlog('------- master saved completed <' + this.entity.typeName + ' ' + this.entity.entityId + '>');
          if (_completeCallBack) _completeCallBack();
        }
      );

      if (doBreak) {
        // tslint:disable-next-line: no-bitwise
        const resOk = (resBeforeSaveCallBack & EN_EntityBeforeSaveCallBackResults.Error_Continue) === 0;
        if (resOk) {
          savedDetails$[0].next();
          savedDetails$[0].complete();
        } else savedDetails$[0].error({ errMsg: cnst_CANCEL_ERROR } as ResultSubject); //raise error callback, but without message box
        return;
      }

      if (!this.loginSubscriptionSave && this.hierarchyLevel === 0) {
        this.loginSubscriptionSave = this.repositoryService.authService.loginAction$.subscribe((resultObject) => {
          if (resultObject.result) { // try one time more
            this.save(_loadedCallBack, _errorCallBack, _completeCallBack, useSuffixInsteadEntityId);
          } else this.unsubscribe(this.loginSubscriptionSave);
        });
      } else {
        if (this.loginSubscriptionSave) this.unsubscribe(this.loginSubscriptionSave); // try only one time more
      }

      //if we have details and a new main entity, we should insert it at first, get id, then save all details, and then update main entity
      const mainObservable$ = savedDetails$[0];
      if (!this.detailItems.isEmpty4Save && this.isNew) {
        const mainInserted$ = new Subject<void>();
        this.saveEntity(mainInserted$, useSuffixInsteadEntityId);
        mainInserted$.subscribe({
          next: () => {
            //save detail Items
            this.detailItems.batchSave(this);
          }, error: (error: any) => {
            mainObservable$.error(error);
          }
        });
      } else {
        //save detail Items
        this.detailItems.batchSave(this);

        //save entity itself - no details objects
        if (this.detailItems.isEmpty4Save) this.saveEntity(mainObservable$, useSuffixInsteadEntityId);
      }

    } else { //to process sub details, when main detail entity is saved, it must call _completeCallBack() anyway
      if ((this.hierarchyLevel > 0) && _loadedCallBack) _loadedCallBack(this.entity);
      this.appLogsService.dlog('!! completed saving main' + this.entity.typeName);
      if (_completeCallBack) _completeCallBack();
      this.resetEditMode();
    }
  }


  private saveEntity(savedDetails$: Subject<void>, useSuffixInsteadEntityId: string) {
    if (!useSuffixInsteadEntityId && this.entityList && (this.isNew && this.dontInsertEntityDirectly || !this.isNew && this.dontUpdateEntityDirectly)) {
      // -- we don't save details data directly, but save the connected list and data should be saved with this.entityList.save, entityId will be the minimal negative
      if (this.entity.entityId === 0) {
        this.entity.entityId = this.entityList.items.reduce<number>((min: number, entity: BaseEntity) => min < entity.entityId ? min : entity.entityId, 0) - 1;
      }
      this.entityWasChanged = true;
      this.appLogsService.dlog('!! next saving main: ' + this.entity.typeName);
      savedDetails$.next();
      this.appLogsService.dlog('!! completed saving main: ' + this.entity.typeName);
      savedDetails$.complete();
    } else {
      this.entity.save(this.repositoryService,
        (entity) => {
          // this.synchroniseEntityList(); //!!! check
          // this.entityWasChanged = false;
          // if (loadedcallback) loadedcallback(entity);
          this.appLogsService.dlog('!! next saving main: ' + this.entity.typeName);
          savedDetails$.next();
        },
        (error: any) => savedDetails$.error(error),
        () => {
          this.appLogsService.dlog('!! completed saving main: ' + this.entity.typeName);
          savedDetails$.complete();
        },
        useSuffixInsteadEntityId
      );
    }
  }


  public synchroniseEntityList(calledAfterSave: EN_SynchronizeListAfter, entityList?: BaseEntityListWrapper<BaseEntity>): boolean {
    if (!entityList) entityList = this.entityList;
    if (!entityList
      || (calledAfterSave === EN_SynchronizeListAfter.New || calledAfterSave === EN_SynchronizeListAfter.Save) && !this.syncEntityListWhenSave
      || calledAfterSave === EN_SynchronizeListAfter.Load && !this.syncEntityListWhenLoad) {
      return false;
    }
    if (calledAfterSave !== EN_SynchronizeListAfter.Load && entityList.reloadAfterItemUpdate) {
      entityList.load();
      entityList.focusedItemId = this.entity.entityId;
    } else entityList.updateListItem(this.entity, this.indexInList4New, calledAfterSave === EN_SynchronizeListAfter.New);
    return true;
  }


  public clear(useNull: boolean = false) {
    this.entity.clear(useNull);
    this.detailItems.clear(useNull);
  }


  public cancelWithSave(_saveCallBack: () => void, _loadedCallBack?: BaseEntityCallBack, _errorCallBack?: ErrorCallBack) {
    this.cancel(_loadedCallBack, _errorCallBack, null, null, _saveCallBack);
  }


  public cancel(_loadedCallBack?: BaseEntityCallBack, _errorCallBack?: ErrorCallBack, _completeCallBack?: () => void, useSuffixInsteadEntityId?: string, _saveCallBack?: () => void) {
    if (!this.confirmationsOn || !this.enabledSaveButton() || !this.entityWasChanged) { //only if something was changed and form is valid
      setTimeout(() => this._cancel(_loadedCallBack, _errorCallBack));
    } else {
      this.confirmMessageBoxYesNoCancel(
        // 'Bearbeitungsabbruch von ' + this.entitytitle,
        this.translate.instant('core.msgbox.header.cancel', { value: this.entitytitle }),
        // `${this.entitytitle} wurde geändert. Sollen Änderungen gespeichert werden?`,
        this.translate.instant('core.msgbox.body.cancel', { value: this.entitytitle }),

        () => {
          if (_saveCallBack) _saveCallBack();
          else this.save(_loadedCallBack, _errorCallBack, _completeCallBack, useSuffixInsteadEntityId);
        },
        () => {
          if (_loadedCallBack) _loadedCallBack(this.entity); //to call close for popupdialogs before canceling starts
          this._cancel(null, _errorCallBack);
        }
      );
    }
  }


  public _cancel(_loadedCallBack?: BaseEntityCallBack, _errorCallBack?: ErrorCallBack): boolean {
    // if (!this.enabledCancelButton()) return false; //cancel must work anyway, even if viemode
    if (this.entityWasChanged || this.isNew) {


      //this.detailItems.cancel();
      if (this.isNew) {
        /*if (this.entityList && this.entityList.focusedItemId) {
          this.entityId = this.entityList.focusedItemId;
        } else*/
        if (this._lastLoadedEntityId) this.entityId = this._lastLoadedEntityId;
        else {
          this.detailItems.cancel();
          this.entity.clear();
          if (this.hierarchyLevel === 0 && this.editModeDefault !== EN_EntityEditingStates.View) this.new();  //if always in edit mode and there is no items in the list the mode stays in new mode
        }
      } else if (this.entity) {
        this.reloadAnyway((entity: BaseEntity) => {
          this.resetEditMode();
          if (_loadedCallBack) _loadedCallBack(this.entity);
        }, _errorCallBack);
        return true;
      }
    }
    this.resetEditMode();
    if (_loadedCallBack) _loadedCallBack(this.entity);
    return true;
  }


  public async deleteAsync(): Promise<boolean> {
    return new Promise<boolean>((resolve, reject) => this.save(() => resolve(true), () => resolve(false) /* complete is called anyway */));
  }


  public async delete(_loadedCallBack?: BaseEntityCallBack, _errorCallBack?: ErrorCallBack) {
    if (!this.confirmationsOn) return this._delete(_loadedCallBack, _errorCallBack);
    this.confirmMessageBoxYesNo(
      // 'Löschen von ' + this.entitytitle,
      this.translate.instant('core.msgbox.header.delete', { value: this.entitytitle }),
      // `Soll ${this.entitytitle} gelöscht werden?`,
      this.translate.instant('core.msgbox.body.delete', { value: this.entitytitle }),
      () => this._delete(_loadedCallBack, _errorCallBack), 1);
  }

  private async _delete(_loadedCallBack?: BaseEntityCallBack, _errorCallBack?: ErrorCallBack) {
    if (this.entity && this.enabledDeleteButton()) {
      let resbeforeDeleteCallBack: EN_EntityBeforeSaveCallBackResults = EN_EntityBeforeSaveCallBackResults.Ok_Continue;
      // tslint:disable-next-line: no-bitwise
      if (this.beforeDeleteCallBack && ((resbeforeDeleteCallBack = await this.beforeDeleteCallBack(this.entity)) & EN_EntityBeforeSaveCallBackResults.Ok_Break) !== 0) {
        // tslint:disable-next-line: no-bitwise
        if ((resbeforeDeleteCallBack & EN_EntityBeforeSaveCallBackResults.Error_Continue) === 0) {
          if (_loadedCallBack) _loadedCallBack(this.entity);
        } else {
          if (_errorCallBack) _errorCallBack({ errMsg: cnst_CANCEL_ERROR } as ResultSubject);
        }
        return;
      }

      let loginSubscription: Subscription;
      if (this.hierarchyLevel === 0) {
        loginSubscription = this.repositoryService.authService.loginAction$.subscribe((resultObject) => {
          if (resultObject.result) { // try one time more
            loginSubscription?.unsubscribe();
            this._delete(_loadedCallBack, _errorCallBack);
          }
        });
      }


      if (this.dontUpdateEntityDirectly) {
        this.resetEditMode();
        if (this.entityList) this.entityList.deleteListItem(this.entity, true);
      } else {
        return this.entity.delete(this.repositoryService,
          (entity) => {
            this.resetEditMode();
            if (this.entityList) this.entityList.deleteListItem(this.entity, false);
            if (_loadedCallBack) _loadedCallBack(entity);
          }
          , (error: ResultSubject) => {
            this.appLogsService.error('---!!! delete error <' + this.entity.typeName + '>:', { error });
            const errorMsg = getErrorMessageAsText(error);
            if (!this.dontShowErrors && errorMsg !== cnst_CANCEL_ERROR) {
              this.errorMessageBox(this.translate.instant('core.error.deleting', { value: this.entitytitle }), errorMsg);
            }
            if (errorMsg !== cnst_CANCEL_ERROR && _errorCallBack) _errorCallBack(error);
          });
      }
    } else {
      if (_loadedCallBack) _loadedCallBack(this.entity);
    }
  }


  public editKey(setEditModedirectly: boolean = false, callBack?: BaseEntityCallBack): T {
    return this._edit(BaseEntityWrapper.EN_KeyEditMode, setEditModedirectly, callBack);
  }



  public edit(setEditModedirectly: boolean = false, callBack?: BaseEntityCallBack): T {
    return this._edit(BaseEntityWrapper.EN_EditMode, setEditModedirectly, callBack);
  }


  public _edit(editMode: EN_EntityEditingStates, setEditModedirectly: boolean = false, callBack?: BaseEntityCallBack): T {
    if (this.enabledEditButton()) {
      this.appLogsService.dlog('--- Edit, since last loading expired ms', now() - (this.entity.lastLoaded || 0));
      if (this.loadedDataExpiresInMs && ((now() - (this.entity.lastLoaded || 0)) > this.loadedDataExpiresInMs)) {
        this.reload(() => this._setEditMode(setEditModedirectly, editMode, callBack));
      } else this._setEditMode(setEditModedirectly, editMode, callBack);
      return this.entity;
    } else return null;
  }


  private _setEditMode(setEditModedirectly: boolean, editMode: EN_EntityEditingStates, callBack: BaseEntityCallBack) {
    if (setEditModedirectly) {
      this.editModeDirectly = editMode;
      if (callBack) callBack(this.entity);
    } else {
      this.editMode = editMode;
      setTimeout(() => {
        if (callBack) callBack(this.entity);
      }, this.delaySetEditModeCallback);
    }
  }


  public delayedEdit(entityId2Edit: number, editFn?: () => any) {
    if (entityId2Edit === this.entityId) {
      if (editFn) editFn();
      else this.edit();
    } else {
      if (!this.delayedEditsubscription) {
        this.delayedEditsubscription = this.entityLoaded$.subscribe(() => {
          this.cancelDelayedEditsubscription();
          if (editFn) editFn();
          else this.edit();
        });
      }
    }
  }


  private cancelDelayedEditsubscription() {
    this.delayedEditsubscription?.unsubscribe();
    this.delayedEditsubscription = null;
  }


  private setfocus4ElementsOf(qualifiedName: keyof HTMLElementTagNameMap, frm: HTMLElement): boolean {
    const elements = frm?.getElementsByTagName(qualifiedName);
    let focusElement = null;
    if (elements) {
      for (let inx = 0; inx < elements.length; inx++) {
        const el = elements[inx];
        if (!(<any>el).disabled && !el.hidden && el.autofocus /*attributes.getNamedItem('autofocus')*/) {
          if (!focusElement) focusElement = el;
          if ((<any>el).required && !(<any>el).value) {
            focusElement = el;
            break;
          }
        }
      }
      setTimeout(() => {
        focusElement?.focus();
      }, 300);
    }
    return !!focusElement;
  }


  public setFocus4Form(formId: string) {
    setTimeout(() => {
      const frm = document.getElementById(formId);
      if (!this.setfocus4ElementsOf('input', frm)) this.setfocus4ElementsOf('button', frm);
    });
  }


  private _NewCopy(copy: boolean): T {
    if (this.editMode === this.editModeDefault || this.editMode === BaseEntityWrapper.EN_NewMode) //we use new() to call everytime before new
    {
      this._lastLoadedEntityId = this.entityId;

      this.editMode = BaseEntityWrapper.EN_NewMode;
      if (!copy) this.entityId = 0;
      if (this.beforeNewCallBack) this.beforeNewCallBack(this.entity);
      if (copy) this.entity.entityId = 0;

      if (copy) setTimeout(() => this.entity.entityChanged = true, 0);

      this.detailItems.new();
      this.updateRequired();
      return this.entity;
    } else return null;
  }


  public new(): T { return this._NewCopy(false); }
  public copy(): T { return this._NewCopy(true); }

  public loadDetails(onlyDetailItem: BaseEntityListWrapper<BaseEntity> | BaseEntityWrapper<BaseEntity>) {
    if (this.detailItems) this.detailItems.load(this, null, 0, onlyDetailItem);
  }


  public reloadAnyway(_loadedCallBack?: BaseEntityCallBack, _errorCallBack?: ErrorCallBack, _completeCallBack?: () => void) {
    this.entity.lastLoaded = 0; //to reload independend from was loaded the last time
    this.reload(_loadedCallBack, _errorCallBack, _completeCallBack);
  }


  public reload(_loadedCallBack?: BaseEntityCallBack, _errorCallBack?: ErrorCallBack, _completeCallBack?: () => void) {
    const ignoreInterval = this.delayToReloadedEntity;
    if ((now() - (this.entity.lastLoaded || 0)) > ignoreInterval) {
      this.appLogsService.dlog(`--- Reload for ${this.entity.entitytitle} is executed: since last loading expired ${now() - (this.entity.lastLoaded || 0)} > ${ignoreInterval}`);
      this.load(this.entityId, _loadedCallBack, _errorCallBack, _completeCallBack); //load new
    } else {
      this.appLogsService.dlog(`--- Reload is ignored: since last loading expired ${now() - (this.entity.lastLoaded || 0)} <= ${ignoreInterval}`);
      this.entityWasLoaded = true;
      if (_loadedCallBack) _loadedCallBack(this.entity);
      if (_completeCallBack) _completeCallBack();
    }
  }


  public hardReload(_loadedCallBack?: BaseEntityCallBack, _errorCallBack?: ErrorCallBack, _completeCallBack?: () => void) {
    const prevDontLoadEntity = this.dontLoadEntity;
    this.dontLoadEntity = false;
    this.load(this.entityId, (entity?: BaseEntity) => {
      this.dontLoadEntity = prevDontLoadEntity;
      if (_loadedCallBack) _loadedCallBack(entity);
    }, _errorCallBack, _completeCallBack); //load new
  }


  public shortcutsHandler(event: KeyboardEvent,
    saveCallBack: () => void = null, _cancelCallBack: () => void = null, _newCallBack: () => void = null, _editCallBack: () => void = null) {
    if (!this.keyDownListenerStopped) {
      if (event.ctrlKey && !event.shiftKey && !event.altKey) {
        // tslint:disable-next-line: deprecation
        switch (event.which) {
          case keycode.E:  // Ctrl + e
            event.stopPropagation();
            if (_editCallBack) _editCallBack();
            else this.edit();
            return false; //processed
          case keycode.S:  // Ctrl + s
            event.stopPropagation();
            if (saveCallBack) saveCallBack();
            else this.save();
            return false; //processed
          case keycode.N:  // Ctrl + n
            event.stopPropagation();
            if (_newCallBack) _newCallBack();
            else this.new();
            return false; //processed
        }
      } else {
        if (!event.ctrlKey && !event.shiftKey && !event.altKey) {
          // tslint:disable-next-line: deprecation
          switch (event.which) {
            case keycode.ESCAPE: // ESC
              event.stopPropagation();
              if (_cancelCallBack) _cancelCallBack();
              else this.cancel();
              return false; //processed event.preventDefault()-the same, event.stopPropagation(); -> globally
          }
        }
      }
    }
    return true; //default processing
  }


  showMessageBox(messageBoxData: IMessageBoxData): MatDialogRef<unknown, any> {
    this.keyDownListenerStopped = true;
    return this.appLogsService.showMessageBox(messageBoxData, () => this.keyDownListenerStopped = false);
  }

  informationMessageBox(caption: string, message: string, callBack?: () => void): MatDialogRef<unknown, any> {
    this.keyDownListenerStopped = true;
    return this.appLogsService.informationMessageBox(caption, message, () => { this.keyDownListenerStopped = false; if (callBack) callBack(); });
  }

  errorMessageBox(caption: string, message: string, okCallBack?: () => void): MatDialogRef<unknown, any> {
    this.keyDownListenerStopped = true;
    return this.appLogsService.errorMessageBox(caption, message, () => { this.keyDownListenerStopped = false; if (okCallBack) okCallBack(); });
  }

  warningMessageBox(caption: string, message: string, okCallBack?: () => void): MatDialogRef<unknown, any> {
    this.keyDownListenerStopped = true;
    return this.appLogsService.warningMessageBox(caption, message, () => { this.keyDownListenerStopped = false; if (okCallBack) okCallBack(); });
  }

  confirmMessageBoxYesNo(caption: string, message: string, yesCallBack: () => void, defaultButtonInx: number = 0): MatDialogRef<unknown, any> {
    this.keyDownListenerStopped = true;
    return this.appLogsService.confirmMessageBoxYesNo(caption, message, yesCallBack, () => this.keyDownListenerStopped = false, defaultButtonInx);
  }

  confirmMessageBoxYesNoCancel(caption: string, message: string, yesCallBack: () => void, noCallBack: () => void, defaultButtonInx: number = 0): MatDialogRef<unknown, any> {
    this.keyDownListenerStopped = true;
    return this.appLogsService.confirmMessageBoxYesNoCancel(caption, message, yesCallBack, noCallBack, () => this.keyDownListenerStopped = false, defaultButtonInx);
  }


}
