import { AfterViewInit, Component, ElementRef, EventEmitter, HostListener, Input, OnDestroy, OnInit, Optional, Output, ViewChild } from '@angular/core';
import * as keycode from '@angular/cdk/keycodes';
import { FormControl, FormGroup, FormsModule, NgForm, ReactiveFormsModule } from '@angular/forms';
import { DxButtonModule, DxDataGridComponent, DxDataGridModule, DxTooltipModule } from 'devextreme-angular';
import { Observable, Subscription } from 'rxjs';
import { map } from 'rxjs/operators';
import { HenselTranslateService } from '../../services/localization/hensel-translate.service';
import {
  MatAutocomplete, MatAutocompleteTrigger, MatFormField, MatOptionSelectionChange, _countGroupLabelsBeforeOption,
  _getOptionScrollPosition
} from '../angular-material-index';
import { CommonModule } from '@angular/common';
import { AngularMaterialModule } from '../angular-material.module';
import { TranslateModule } from '@ngx-translate/core';


const enum EN_DropDownState {
  Closed = 0,
  Opened = 1,
  EmptyFilter = 2,
}

@Component({
  // tslint:disable-next-line: component-selector
  selector: 'hensel-selection',
  templateUrl: './hensel-selection.component.html',
  styleUrls: ['./hensel-selection.component.scss'],
  standalone: true,
  imports: [
    CommonModule,
    AngularMaterialModule,
    FormsModule,
    ReactiveFormsModule,
    DxButtonModule,
    DxTooltipModule,
    TranslateModule,
    DxDataGridModule
  ]
})

export class HenselSelectionComponent implements OnInit, AfterViewInit, OnDestroy {

  @ViewChild(MatAutocompleteTrigger) autocompleteTrigger: MatAutocompleteTrigger;
  @ViewChild('cbInput') cbInput: ElementRef;
  @ViewChild('hs_panel') autocompleteScrollPanel: ElementRef;
  @ViewChild('hs_toolbar') autocompleteToolbar: ElementRef;
  @ViewChild('hs_matformfield') hsMatFormField: MatFormField;
  @ViewChild('grid_hensel') grid_hensel: DxDataGridComponent;
  @ViewChild('hensel_selection') hsElementRef: ElementRef;

  // the dataSource of selectable items
  public EN_DropDownStateClosed: EN_DropDownState = EN_DropDownState.Closed;

  public _dataSource: any[] = [];
  private _origDataSource: any[];
  private currentScrollTop: number = 0;
  private hsRequiredsubscription: Subscription;

  private lastDisabled: boolean;
  private lastShowSpinner: boolean;

  @Input() public set dataSource(value: any[]) {
    this._origDataSource = value;
    this.applyCustomFilterAsync();
  }

  @Input() public set reprepareImmedeately(value: boolean) {
    if (value) {
      this.applyCustomFilterAsync();
      setTimeout(() => { //to avoid change after chek error
        this.reprepareImmedeatelyChange.emit(this.reprepareImmedeately);
      });
    }
  }
  public get reprepareImmedeately() { return false; }
  @Output() reprepareImmedeatelyChange = new EventEmitter<any>();


  private _keyExpr: string;
  public get keyExpr(): string {
    return this._keyExpr;
  }
  @Input() public set keyExpr(value: string) {
    this._keyExpr = value;
    this.applyCustomFilterAsync();
  }


  private _displayExpr: string;
  public get displayExpr(): string {
    return this._displayExpr;
  }
  @Input() public set displayExpr(value: string) {
    this._displayExpr = value;
    this.applyCustomFilterAsync();
  }


  private _required: boolean;
  public get required(): boolean {
    return this._required;
  }
  @Input() public set required(value: boolean) {
    if (typeof (value) === 'string') value = value !== 'false';
    this.setRequired(value);
    console.log('**** - Hensel-Sel: ' + this.placeholder + ' set required', value);
    this.updateParent();
  }

  private orgRequired: boolean;

  private _disabled: boolean = false;
  public get disabled(): boolean {
    return this._disabled;
  }

  @Input()
  public set disabled(value: boolean) {
    value = typeof (value) === 'string' ? value !== 'false' : value
    if (!this.showSpinner) {
      this.setDisable(value);
    }
    this.lastDisabled = value;
  }

  // binding for selected items
  private _selectedFirst: boolean = true;
  public get selectedFirst(): boolean {
    return this._dataSource && this._dataSource.length > 200 || !this.multiSelect ? false : this._selectedFirst;
  }
  @Input() public set selectedFirst(value: boolean) {
    this._selectedFirst = value;
    this.itemsControl.setValue(this.lastFilter);
  }

  @Input() public validate: (items: any[]) => boolean;

  // ',' delimited & conditions without spaces (space means compare against null)
  private _customFilter: string[] = [];

  @Input() public set customFilter(value: string) {
    if (!value) this._customFilter = [];
    else this._customFilter = value.split(',');
    this.applyCustomFilterAsync();
  }

  private _customFilterFn: (item: any) => boolean = null;

  @Input()
  public get customFilterFn(): (item: any) => boolean {
    return this._customFilterFn;
  }
  public set customFilterFn(value: (item: any) => boolean) {
    this._customFilterFn = value;
    this.applyCustomFilterAsync();
  }

  public _selectedItemIds: any[] = [];
  @Input() public set selectedItems(val: any[] | any) {
    if (val instanceof Array) this.setValueInternally(val.slice(), true); //use copy of array
    else this.setValueInternally(val, true);
    this.lastSelection = this._selectedItemIds;
    // console.log('!!!!!! ' + this.placeholder, val, this._selectedItemIds.length);
  }

  public get selectedItems(): any[] | any {
    return this._selectedItemIds;
  }

  @Output() selectedItemsChange = new EventEmitter<any>();
  @Output() selectedItemsDisplayChange = new EventEmitter<string>();
  @Output() dropDownClosed = new EventEmitter();
  // Name of dataSource
  @Input() placeholder: string;
  @Input() multiSelect: boolean = true;
  @Input() separator: string = ',';
  @Input() width: string = '100%';
  @Input() height: string = '200px';
  @Input() displayTemplate: string = ''; //"${filedname}: ${filedname}"
  @Input() displayIconFn: (item: any) => string;
  @Input() id: string = '';
  @Input() name: string = '';
  @Input() useCloseButton: boolean = true;
  @Input() useMarkFiltered: boolean = true;
  @Input() useSelFirstButton: boolean = false;
  @Input() useCBChangedColor: boolean = true;
  private _useResultGrid: boolean = false;
  @Input()
  public get useResultGrid(): boolean {
    return this._useResultGrid;
  }
  public set useResultGrid(value: boolean) {
    this._useResultGrid = value;
  }

  @Input() useLabelChangedColor: boolean = true;
  @Input() backgroundColor: string = 'inherit';
  public backgroundColor4Grid: string;


  private _minFilterLength4Search: number = -1;
  public get minFilterLength4Search(): number {
    let _minFilterLength4Search = 0;
    if (this._minFilterLength4Search === 0) {
      _minFilterLength4Search = this.getMinFilterLength4Search(this._dataSource.length);
    } else {
      if (this._minFilterLength4Search > 0) _minFilterLength4Search = this._minFilterLength4Search;
    }
    return (this._customFilter.length === 0 && !this._customFilterFn) ? _minFilterLength4Search :
      Math.min(this.getMinFilterLength4Search(this._dataSource.length), _minFilterLength4Search);
  }

  @Input() public set minFilterLength4Search(value: number) {
    this._minFilterLength4Search = value;
  }
  private _sortField: string = null;
  private sortDirection: -1 | 1 = 1;
  @Input() set sortField(value: string) /*with leading "-" descendent*/ {
    this._sortField = value;
    if (this._sortField && this._sortField.slice(0, 1) === '-') {
      this.sortDirection = -1;
      this._sortField = this._sortField.slice(1);
    } else this.sortDirection = 1;
    this.applyCustomFilterAsync();
  }
  get sortField(): string { return this._sortField; }

  public get formControlName(): string {
    return 'henselSelectionControl_' + this.name;
  }


  private _parent: NgForm = null;
  public get parent(): NgForm {
    return this._parent;
  }

  @Input()
  public set parent(value: NgForm) {
    this._parent = value ?? this._parentForm;
    this.hsRequiredsubscription?.unsubscribe();
    this.hsRequiredsubscription = this.parent?.valueChanges.subscribe(() => this.checkRequired()); //take cares about form.invalid, if the component invalid
      // this.formGroup?.addControl(this. formControlName, this.itemsControl); //if we add control to partner, form get changed event on dropdown
    this.markAsTouchedDirty();
  }

  private _newfilter: string;

  public get openWithFilter(): string {
    return this._newfilter;
  }

  @Input()
  public set openWithFilter(newfilter: string) {
    this._newfilter = newfilter;
    if (this.showSpinner || !this.initialised || !this._origDataSource || this._origDataSource.length === 0) return; //to avoid repeatedly calls during initialising

    if (newfilter !== undefined) {
      setTimeout(() => {
        this.itemsControl.setValue(newfilter);
        if (!!newfilter) this.cbInput?.nativeElement?.focus();
      });
    }
  }

  @Input() public hsClass: string = '';

  @Input() selectionNotFound: (arg: number) => string;
  @Input() autofocus: any = false;

  private _canClear: boolean = true;
  public get canClear(): boolean {
    return this._canClear;
  }
  @Input() public set canClear(value: boolean) {
    this._canClear = value;
  }
  @Input() public defaultValue: any;
  @Output() public focus = new EventEmitter<HenselSelectionComponent>();
  @Input() public tabindex: number = 0;

  private _title: string = '';
  public get title(): string {
    let resTitle = this._title;
    if (!resTitle && this.cbInput && this.cbInput.nativeElement && this.cbInput.nativeElement.clientWidth < this.cbInput.nativeElement.scrollWidth) {
      resTitle = this.displayFn(this._selectedItemIds);
    }
    return resTitle;
  }

  @Input()
  public set title(value: string) {
    this._title = value;
  }

  private _showSpinner: boolean = false;
  public get showSpinner(): boolean {
    return this._showSpinner;
  }

  @Input()
  public set showSpinner(value: boolean) {
    this.setShowSpinner(value);
    this.lastShowSpinner = value;
    this.openWithFilter = this.openWithFilter;
  }

  private _disabledItems: any[] = [];
  public get disabledItems(): any[] {
    return this._disabledItems;
  }
  @Input()
  public set disabledItems(value: any[]) {
    this._disabledItems = value;
    this.applyCustomFilterAsync();
  }


  public itemsControl = new FormControl();

  public get displayLength(): number {
    return this.displayFn(this._selectedItemIds)?.length;
  }

  public hasFocus: boolean = false;
  public filteredItems: any[] = [];
  public items2filter: Observable<any[]>;
  public selectedItemsGrid: any[] = [];
  public selectedAllNonePart: number = 0; //-1 - none, 0 - part, 1 - all
  private _dropDownState: EN_DropDownState = EN_DropDownState.Closed; //0-closed, 1-opened, 2-not shown because of empty filter
  public get dropDownState(): EN_DropDownState {
    return this._dropDownState;
  }
  public set dropDownState(value: EN_DropDownState) {
    if (value === EN_DropDownState.Closed && this._dropDownState !== value) this.dropDownClosed.emit();
    this._dropDownState = value;
  }
  public lastFilter: string = '';
  private _lastSelectionString: string = '';
  private lastSelectedItemIds: any = '';
  public get lastSelection(): any {
    return this._lastSelectionString;
  }
  public set lastSelection(value: any) {
    this.lastSelectedItemIds = value.slice();
    this._lastSelectionString = this.getSelection(value);
  }
  public startLeaveFocus: boolean = false;
  public showInputField: boolean = true;

  public initialised: boolean = false;
  private lastActiveItemIndex: number;

  private get formGroup(): FormGroup {
    return this.parent?.control;
  }

  constructor(
    public translate: HenselTranslateService,
    @Optional() private _parentForm: NgForm
  ) {
    this.applyCustomFilterAsync = this.applyCustomFilterAsync.bind(this);
    translate.onLangChange.subscribe(() => this.applyCustomFilterAsync());
  }

  ngOnInit() {
    //document.getElementById('cbInput').addEventListener('blur', this.closeDropDown); //can be used (blur)="closeDropDown()" in html
    //document.getElementById('cbInput').addEventListener('focus', this.openDropDown);
    this.items2filter = this.itemsControl.valueChanges.pipe(
      //      startWith<string | any[]>(''),
      // debounceTime(300),
      map(value => typeof value === 'string' ? value : this.lastFilter),
      map(filter => this.applyFilter(filter)),
    );

    this.items2filter.subscribe(() => setTimeout(() => { //over time out - the list must be built
      this.applyMarkFiltered(this.lastFilter);
    }));

    this.showInputField = !this.useResultGrid;
    setTimeout(() => {
      if (!this.parent) this.parent = this._parent;
    }, 0);
  }

  ngAfterViewInit(): void {
    if (this.lastShowSpinner === undefined) this.setShowSpinner(true);
    setTimeout(() => {
      this.initialised = true;
      this.applyCustomFilterAsync();
      this.backgroundColor4Grid = this.getBackgroundColor(this.hsElementRef.nativeElement);
    }, 0);
  }


  private getMinFilterLength4Search(count: number) {
    return count > 5000 ? 4 : (count > 1000 ? 3 : (count > 300 ? 2 : 0));
  }


  private setShowSpinner(value: boolean) {
    //dettimeout interferiert with settimeout in open with filter
    setTimeout(() => { //timeout because of ExpressionChangedAfterItHasBeenCheckedError
      this._showSpinner = value;
      if (value) this.setDisable(true);
      else this.setDisable(this.lastDisabled);
      this.openWithFilter = this.openWithFilter;
    }, 0);
  }


  private setDisable(value: boolean) {
    this._disabled = value;
    if (this.disabled) {
      this.autocompleteTrigger?.closePanel();
      this.itemsControl.disable();
    } else this.itemsControl.enable();
  }


  private setRequired(value: boolean) {
    this._required = value;
  }


  private setValueInternally(value: any[] | any, externally = false) {

    if (!this._selectedItemIds) this._selectedItemIds = [];
    if (value instanceof Array) this._selectedItemIds.splice(0, this._selectedItemIds.length, ...value); //we replace here elements of array, otherwise the connected array will not be set w/o emit and values are not reset
    // this._selectedItemIds = val;
    else {
      this._selectedItemIds.splice(0);
      if (value) this._selectedItemIds.push(value);
    }

    this.updateGrid();

    // const lastState = this.dropDownState;
    // this._dropDownState = EN_DropDownState.Opened; // to update the 1st time the dropdown ?? //results in keyinput delays
    this.itemsControl.setValue(this._selectedItemIds);
    console.log('**** - Hensel-Sel: ' + this.placeholder + ' set value internaly', value);
    if (externally) this.updateParent(); //otherwise if set programatically parent form doesn't change valid status
    // this._dropDownState = lastState;
  }


  getBackgroundColor(htmlElemet: any): string {
    const backgroundColor = window.getComputedStyle(htmlElemet, null).getPropertyValue('background-color');
    if (backgroundColor !== 'rgba(0, 0, 0, 0)' || !htmlElemet?.parentElement) return backgroundColor;
    else return this.getBackgroundColor(htmlElemet.parentElement);
  }

  public checkInSelected(val: any) {
    return this._selectedItemIds.indexOf(val[this.keyExpr]) >= 0;
  }

  public checkChanged(val: any) {
    const isSelected = this.checkInSelected(val);
    const wasSelected = this.lastSelectedItemIds.indexOf(val[this.keyExpr]) >= 0;
    // tslint:disable-next-line: no-bitwise
    return <number><any>isSelected ^ <number><any>wasSelected;
  }

  // @HostListener('window:keydown', ['$event']) - if not commented we get delay in keyinput for other input fields
  public onKeyDown = (event?: KeyboardEvent): boolean => {
    // tslint:disable-next-line: deprecation
    if (event.keyCode === keycode.ENTER && this.multiSelect) {
      this.selectionDone();
      event.stopPropagation();
      return false; //no further processing
    }
    return true; //default processing
  }

  @HostListener('keydown', ['$event'])
  public inputFilter(event?: KeyboardEvent) {
    // tslint:disable-next-line: deprecation
    if (event.keyCode === keycode.ENTER || event.keyCode === keycode.TAB && !this.multiSelect) //return
    {
      if (this.filteredItems.length === 1) this.toggleSelection(this.filteredItems[0]);
      if (!this.multiSelect) this.cbInput.nativeElement.blur();
      // tslint:disable-next-line: deprecation
      if (event.keyCode === keycode.ENTER) {
        event.stopPropagation();
        return false; //was processed
      } else return true;
      // tslint:disable-next-line: deprecation
    } else if (event.keyCode === keycode.UP_ARROW || event.keyCode === keycode.DOWN_ARROW) {
      if (this.multiSelect) this.autocompleteTrigger.autocomplete._keyManager.setActiveItem(null);
      else {
        if (this.lastActiveItemIndex !== undefined) {
          // tslint:disable-next-line: deprecation
          this.autocompleteTrigger.autocomplete._keyManager.setActiveItem((this.lastActiveItemIndex + (event.keyCode === keycode.UP_ARROW ? -1 : 1) + this._dataSource.length) % this._dataSource.length);
        }
        this.lastActiveItemIndex = undefined;
        this.scrollToOption();
      }
      event.stopPropagation();
      return false; //was processed
    } else if (event.keyCode !== keycode.TAB && event.keyCode !== keycode.ESCAPE && !this.lastFilter
      || event.keyCode === keycode.ESCAPE && this.lastFilter) { //otherwise is cleared with tab
      this.itemsControl.setValue(''); //clear filter if it was empty
    }
    return true; //default processing
  }

  itemSelectedByKeyboard(event: MatOptionSelectionChange) {
    if (event.isUserInput && this.autocompleteTrigger.autocomplete._keyManager.activeItem) this.toggleSelection(this.filteredItems[this.autocompleteTrigger.autocomplete._keyManager.activeItemIndex]);
  }

  private displayItem(item: any, init: boolean = false): string {
    if (init) {
      const caption = this.translate.instant(item[this.displayExpr]);
      if (this.displayTemplate) return this.displayTemplate.replace(/\${(.*?)}/g, (found: string, fldname: string) => item[fldname] ?? '').replace('<key>', item[this.keyExpr] ?? '').replace('<name>', caption);
      else return caption;
    } else return item[this.displayExpr];
  }

  checkRequired() {
    if (this.requiredError()) this.formGroup?.setErrors({ required: true });
  }

  requiredError(): boolean {
    let req = this.required;
    if (this.dropDownState !== EN_DropDownState.Closed) req = this.orgRequired;
    return !this.disabled && req !== undefined && req !== false && this.isEmpty();
  }


  public applyFilter(filter: string): any[] {

    this.lastFilter = filter;
    if (this.dropDownState !== EN_DropDownState.Closed) {

      if (filter.length < this.minFilterLength4Search) this.filteredItems.splice(0);
      else
        if (filter.length > 0) {
          const _filter = filter.toLowerCase();
          this.filteredItems = this._dataSource.filter(item => this.displayItem(item).toLowerCase().indexOf(_filter) >= 0);
        } else this.filteredItems = this._dataSource.slice();

      if (this.selectedFirst) {
        this.filteredItems.sort((item1, item2) => {
          let retval: number = 0;
          if (this.selectedFirst) retval = <number><any>this.checkInSelected(item2) - <number><any>this.checkInSelected(item1);
          if (retval === 0 && this._sortField) retval = !item1[this._sortField] ? -1 : this.compare2items(item1, item2);
          return retval;
        }
        );
      }

      const noSelectedInList: number = this.isEmpty() ? 0 : this.filteredItems.filter(item => this._selectedItemIds.indexOf(item[this.keyExpr]) >= 0).length;
      this.selectedAllNonePart = noSelectedInList === 0 ? -1 : (noSelectedInList === this.filteredItems.length ? 1 : 0);

      //no close event is called if filter empty, but drop down is closed - therefore we set status manuelly
      if (this.dropDownState === EN_DropDownState.Opened && this.filteredItems.length === 0) this.dropDownState = EN_DropDownState.EmptyFilter;
    }
    return this.filteredItems;
  }


  public displayFn = (value: any[] | string): (string | undefined) => {  //such declaration because of binding
    let displayValue: string = '';
    if (Array.isArray(value)) {
      //      value.forEach((key, index) => displayValue += (index === 0 ? '' : this.separator + ' ') + this._dataSource.find(op => op[this.keyExpr] === key)[this.displayExpr]);
      if (this._dataSource) {
        value.forEach(vl => {
          const item = this._dataSource.find((it) => it[this.keyExpr] === vl);
          if (item) {
            displayValue += this.separator + ' ' + this.displayItem(item);
          } else if (this.selectionNotFound) displayValue += this.separator + ' ' + this.selectionNotFound(vl);
        });
        /*
                this._dataSource.forEach((item, index) => {
                  if (value.indexOf(item[this.keyExpr]) >= 0) {
                    displayValue += this.separator + ' ' + this.displayItem(item);
                  }
                });*/
      }
      displayValue = displayValue.slice(this.separator.length + 1);
    } else displayValue = value;

    return displayValue;
  }


  private prepareDataSource() {
    if (this._customFilter.length > 0) {
      this._dataSource = this._origDataSource.filter(item => {
        return !this._customFilter.some(filterToken => { //_customFilter tokens are connected with && condition
          let tokens: string[] = filterToken.split('!=');
          if (tokens.length === 2) {
            if (tokens[1] === ' ') return (item[tokens[0]] === null);
            return !('' + item[tokens[0]] !== tokens[1]);
          }
          tokens = filterToken.split('=');
          if (tokens.length === 2) {
            return !('' + item[tokens[0]] === tokens[1]);
          }
          tokens = filterToken.split('<');
          if (tokens.length === 2) {
            if (!Number.isNaN(Number(item[tokens[0]])) && !Number.isNaN(Number(tokens[1]))) {
              return !(Number(item[tokens[0]]) < Number(tokens[1]));
            } else return !('' + item[tokens[0]] < tokens[1]);
          }
          tokens = filterToken.split('>');
          if (tokens.length === 2) {
            if (!Number.isNaN(Number(item[tokens[0]])) && !Number.isNaN(Number(tokens[1]))) {
              return !(Number(item[tokens[0]]) > Number(tokens[1]));
            } else return !('' + item[tokens[0]] > tokens[1]);
          }
          tokens = filterToken.split('<=');
          if (tokens.length === 2) {
            if (!Number.isNaN(Number(item[tokens[0]])) && !Number.isNaN(Number(tokens[1]))) {
              return !(Number(item[tokens[0]]) <= Number(tokens[1]));
            } else return !('' + item[tokens[0]] <= tokens[1]);
          }
          tokens = filterToken.split('>=');
          if (tokens.length === 2) {
            if (!Number.isNaN(Number(item[tokens[0]])) && !Number.isNaN(Number(tokens[1]))) {
              return !(Number(item[tokens[0]]) >= Number(tokens[1]));
            } else return !('' + item[tokens[0]] >= tokens[1]);
          }
          return false;
        });
      });
    } else this._dataSource = this._origDataSource.slice();
    if (this.customFilterFn) this._dataSource = this._dataSource.filter(item => this.customFilterFn(item));
  }


  private async applyCustomFilterAsync() {
    if (!this.initialised || !this._origDataSource) return; //to avoid repeatedly calls during initialising

    const dataSourceWasEmpty = !this._dataSource?.length;
    this.prepareDataSource();

    if (!this.displayExpr) this._displayExpr = 'display';

    if (this._sortField === '*') this._sortField = this.displayExpr;

    this._dataSource = this._dataSource.map((item, index, arr) => {
      if (this._sortField && this._sortField !== this.displayExpr && this._sortField !== this.keyExpr) {
        return Object({
          [this.keyExpr]: item[this.keyExpr],
          [this.displayExpr]: this.displayItem(item, true),
          [this.sortField]: this._sortField ? item[this._sortField] : '',
          link: item,
          disabled: this.disabledItems.indexOf(item[this.keyExpr]) > -1
        });
      } else {
        return Object({
          [this.keyExpr]: item[this.keyExpr],
          [this.displayExpr]: this.displayItem(item, true),
          link: item,
          disabled: this.disabledItems.indexOf(item[this.keyExpr]) > -1
        });
      }
    });

    //checks if the passed value for the selection in the filtered selection list, if not - delete
    let selWasChanged: boolean = false;
    if (this._dataSource.length > 0 || !dataSourceWasEmpty) { //w/o checking dataSourceWasNotEmpty after deleting the dataSource, selected value is kept
      const filterIds = this._dataSource.map(item => item[this.keyExpr]);
      for (let inx = this._selectedItemIds.length - 1; inx >= 0; inx--) {
        if (filterIds.indexOf(this._selectedItemIds[inx]) === -1) {
          if (!this.selectionNotFound) {
            this._selectedItemIds.splice(inx, 1);
            selWasChanged = true;
          }
        }
      }
    }


    if (this._sortField) this._dataSource.sort((item1, item2) => this.compare2items(item1, item2));

    console.log('**** - Hensel-Sel: ' + this.placeholder + ' current id=', this._selectedItemIds, 'lenght=', this._dataSource.length, ' wasempty=', !dataSourceWasEmpty, 'waschanged=', selWasChanged);
    this.itemsControl.setValue(this._selectedItemIds);

    this.updateGrid();

    if (this._origDataSource.length > 0 && this.lastShowSpinner === undefined) this.setShowSpinner(false); //if was not set explicetely
    else this.openWithFilter = this.openWithFilter; //only in else, bcs is set in setShowSpinner(false)

    if (selWasChanged) {
      console.log('**** - Hensel-mark ' + this.placeholder + ' indirect changed');
      setTimeout(() => this.updateParent());
    }
    this.markAsTouchedDirty();
  }


  updateGrid() {
    if (this.useResultGrid) {
      if (this._dataSource && this._selectedItemIds && this._selectedItemIds.length > 0) {
        this.selectedItemsGrid = this._dataSource.filter((item: any) => this._selectedItemIds.indexOf(item[this.keyExpr]) >= 0);
      } else this.selectedItemsGrid = [];
    }
  }


  compare2items = (item1, item2): number => {
    if (item1[this._sortField]) {
      if (typeof (item1[this._sortField]) === 'number') return (item1[this._sortField] - item2[this._sortField]) * this.sortDirection;
      else return item1[this._sortField].localeCompare(item2[this._sortField]) * this.sortDirection;
    } else return 0;
  }


  private applyMarkFiltered(filter: string) {
    if (!this.useMarkFiltered || this.dropDownState !== EN_DropDownState.Opened) return;
    const inputTexts = this.autocompleteScrollPanel.nativeElement.getElementsByClassName('item');
    if (!inputTexts) return;
    for (let index = 0; index < Math.min(this.filteredItems.length, inputTexts.length); index++) {
      const iconHtmlInd = inputTexts[index].innerHTML.indexOf('</mat-icon>');
      const iconHtml = iconHtmlInd < 0 ? '' : inputTexts[index].innerHTML.slice(0, iconHtmlInd + 11);
      if (filter === '') inputTexts[index].innerHTML = iconHtml + this.displayItem(this.filteredItems[index]);
      else {      //inputTexts[index].innerHTML = this.filteredItems[index][this.displayExpr].replace(filter, '<span class="hs_marked">' + filter + '</span>');
        inputTexts[index].innerHTML = iconHtml + this.displayItem(this.filteredItems[index]).replace(
          new RegExp(filter + '(?!([^<]+)?<)', 'gi'), '<b class="hs_marked">$&</b>');
      }
    }
  }


  public itemClicked(event: Event, item: any) {
    event.stopPropagation();
    this.toggleSelection(item);
  }


  public toggleSelection(item: any) {
    if (item.disabled) return;
    if (!this.multiSelect) this._selectedItemIds = [];
    if (!this.checkInSelected(item)) {
      this._selectedItemIds.push(item[this.keyExpr]);
    } else {
      const i = this._selectedItemIds.indexOf(item[this.keyExpr]);
      this._selectedItemIds.splice(i, 1);
    }

    if (!this.minFilterLength4Search) {
      //show the complete list again (multiselect), after checking an option
      //is not working if we have limitations for search
      this.lastFilter = '';
      this.itemsControl.setValue(this.lastFilter);
    }

    if (!this.multiSelect) {
      this.itemsControl.setValue(this.displayFn(this._selectedItemIds));
      this.selectionDone();
    } else this.itemsControl.setValue(this._selectedItemIds);

  }



  public isEmpty(): boolean {
    return this._selectedItemIds.length === 0;
  }


  public openDropDown = () => { //such declaration because of binding
    if (this.disabled) return;
    if (this.dropDownState === EN_DropDownState.Closed) {//if is opened at the first time
      if (this.multiSelect) window.addEventListener('keydown', this.onKeyDown, { once: true } as AddEventListenerOptions /*fires one time and is removed*/);
      this.orgRequired = this.required;
      if (!this.isEmpty()) this.setRequired(false);
      this.lastSelection = this._selectedItemIds;
      if (!this.multiSelect && this._selectedItemIds.length > 0) {
        setTimeout(() => {
          this.lastActiveItemIndex = this._dataSource.findIndex(item => item[this.keyExpr] === this._selectedItemIds[0]);
          this.autocompleteTrigger.autocomplete._keyManager.setActiveItem(this.lastActiveItemIndex);
          this.scrollToOption();
        }, 0);
      }
    }
    if (this.filteredItems.length > 0) this.dropDownState = EN_DropDownState.Opened;
    else {
      if (this.dropDownState === EN_DropDownState.Opened && this.autocompleteTrigger.panelOpen) this.autocompleteTrigger.closePanel();
      this.dropDownState = EN_DropDownState.EmptyFilter;
    }
    this.itemsControl.setValue(this.lastFilter); // important the order, must be after setting of dropDownIsOpened
  }


  scrollToOption() {
    const autocomplete: MatAutocomplete = <any>this.autocompleteTrigger.autocomplete;
    const index = autocomplete._keyManager.activeItemIndex || 0;
    const labelCount = _countGroupLabelsBeforeOption(index, autocomplete.options, autocomplete.optionGroups);
    if (index === 0 && labelCount === 1) {
      // If we've got one group label before the option and we're at the top option,
      // scroll the list to the top. This is better UX than scrolling the list to the
      // top of the option, because it allows the user to read the top group's label.
      autocomplete._setScrollTop(0);
      // this.autocompleteScrollPanel.nativeElement.scrollTop = 0;
    } else if (index > -1 && autocomplete.panel) {
      const panelheight = autocomplete.panel.nativeElement.offsetHeight - (this.autocompleteToolbar?.nativeElement.offsetHeight || 0);
      const itemHeight = (<any>autocomplete.options.first)?._element?.nativeElement?.offsetHeight || 30;
      this.currentScrollTop = _getOptionScrollPosition((index + labelCount) * itemHeight, itemHeight, this.currentScrollTop, panelheight);
      autocomplete._setScrollTop(this.currentScrollTop);
      // this.autocompleteScrollPanel.nativeElement.scrollTop = this.currentScrollTop;
    }
  }


  public closeDropDown = () => {  //such declaration because of binding
    this.currentScrollTop = 0;
    if (this.hasFocus && this.dropDownState === EN_DropDownState.EmptyFilter) return; //the input box is still focused

    setTimeout(() => {
      this.lastFilter = '';
    }); //timeout is needed to avoid open/close drop down effect after click out of component
    this.setValueInternally(this.lastSelectedItemIds);
    this.dropDownState = EN_DropDownState.Closed;
    this.setRequired(this.orgRequired);
    if (this.multiSelect) window.removeEventListener('keydown', this.onKeyDown, { once: true } as AddEventListenerOptions /*fires one time and is removed*/);
  }


  public takeFocus = () => {  //such declaration because of binding
    this.startLeaveFocus = false;
    this.hasFocus = true;
    this.openDropDown();
    this.focus.emit(this);
  }


  public leaveFocus = () => {  //such declaration because of binding
    this.hasFocus = false;
    if (this.dropDownState !== EN_DropDownState.EmptyFilter) return; //reset the last value if searched string was not matched
    this.startLeaveFocus = true;
    setTimeout(() => { //gives time to cancel closeDropdown if the clear button is focused
      if (this.startLeaveFocus) this.closeDropDown();
      this.startLeaveFocus = false;
    });
  }

  public setAll(completed: boolean) {
    if (completed) this.selectAll();
    else this.deselectAll();
  }


  public selectAll() {
    this.setValueInternally(this._selectedItemIds.concat(this.filteredItems.map(item => item[this.keyExpr]).filter(item => this._selectedItemIds.indexOf(item) === -1)));
  }


  public deselectAll() {
    const filterIds = this.filteredItems.map(item => item[this.keyExpr]);
    this.setValueInternally(this._selectedItemIds.filter(id => filterIds.indexOf(id) === -1));
  }


  public getSelection(val: any[]): string {
    return val.sort((a, b) => a - b).join();
  }


  public updateParent() {
    this.formGroup?.updateValueAndValidity();
  }


  public selectionDone() {
    const curSelection = this.getSelection(this._selectedItemIds);
    if (this.lastSelection !== curSelection) {
      if (this.validate && !this.validate(this._selectedItemIds)) return;
      this.lastSelection = this._selectedItemIds;
      console.log('**** - Hensel-Sel: ' + this.placeholder + ' selection done');
      this.updateParent();
      this.selectedItemsChange.emit(this.multiSelect ? this._selectedItemIds.slice() : (this._selectedItemIds.length > 0 ? this._selectedItemIds[0] : null));
      this.selectedItemsDisplayChange.emit(this.displayFn(this._selectedItemIds));
    }
    this.autocompleteTrigger.closePanel();
  }


  public clickClear($event: any) {
    $event.stopPropagation();
    if (/*this.dropDownState === EN_DropDownState.Opened ||*/ this.lastFilter) {
      setTimeout(() => {
        this.itemsControl.setValue('');
        this.cbInput?.nativeElement?.focus(); //to call opendropdown after close drop down if minlength>0
      }, 300);
    } else {
      this.lastSelection = this._selectedItemIds;
      if (this.defaultValue !== undefined && this.defaultValue !== null) this.setValueInternally([this.defaultValue]);
      else this.setValueInternally([]);
      this.selectionDone();
    }
  }


  clickArrow(event?: any) {
    if (this.disabled) return;
    this.showInputField = true;
    setTimeout(() => { this.cbInput?.nativeElement?.focus(); }, 0);
    if (event) event.cancel = true;
  }

  ngOnDestroy(): void {
    if (this.hsRequiredsubscription) this.hsRequiredsubscription.unsubscribe();
  }

  getIcon4Selected(): string {
    if (this.displayIconFn) return this.displayIconFn(this._dataSource.find(_item => _item[this.keyExpr] === this._selectedItemIds[0])?.link);
    return null;
  }

  public markAsDirty() {
    if (this.hsMatFormField?._control?.ngControl?.control) {
      this.hsMatFormField._control.ngControl.control.markAsDirty();
      console.log('**** - Hensel-mark ' + this.placeholder + ' as dirty');
      this.updateParent();
    }
  }


  public markAsTouched() {
    if (this.hsMatFormField?._control?.ngControl?.control) {
      this.hsMatFormField._control.ngControl.control.markAsTouched();
      console.log('**** - Hensel-mark ' + this.placeholder + ' as touched');
      this.updateParent();
    }
  }


  public markAsTouchedDirty() {
    if (this.hsMatFormField?._control?.ngControl?.control && this.required && this.isEmpty()) {
      this.hsMatFormField._control.ngControl.control.markAsTouched();
      this.hsMatFormField._control.ngControl.control.markAsDirty();
      console.log('**** - Hensel-mark ' + this.placeholder + ' as touched/dirty');
      this.updateParent();
    }
  }

}
