/* eslint-disable @typescript-eslint/member-ordering */
/*
 * Copyright (C) 2023 Robert Bosch GmbH Copyright (C) 2023 Robert Bosch Manufacturing Solutions GmbH, Germany. All rights reserved.
 */

import {
  ChangeDetectorRef,
  Component,
  EventEmitter,
  Input,
  OnDestroy,
  OnInit,
  Output,
  TemplateRef,
} from '@angular/core';
import { combineLatest, fromEvent, Observable, Subject } from 'rxjs';
import { map, startWith, takeUntil } from 'rxjs/operators';

import { BciCorePicklistIntl } from '../bci-core-picklist-intl';
import { CoreDragInstance, CoreDragState } from '../bci-core-picklist.model';
import { BciCorePicklistService } from '../bci-core-picklist.service';
import {
  BciCorePicklistFilter,
  BciCorePicklistMode,
  BciCorePicklistResponsibility,
  BciCorePicklistSorting,
  BciCorePicklistStringFilter,
} from '../bci-core-picklist.types';

/**
 * A picklist component for selecting items from a list of available items. The list of available items is referred to as source, and
 * the list of selected as target list. Both source and target list as well as this component interact using the {@link BciCorePicklistService}.
 */
@Component({
  selector: 'bci-core-picklist',
  templateUrl: './bci-core-picklist.component.html',
  styleUrls: ['./bci-core-picklist.component.scss'],
  providers: [BciCorePicklistService],
})
export class BciCorePicklistComponent implements OnInit, OnDestroy {
  /** Whether it is mobile view. */
  isMobile$: Observable<boolean>;
  checkScreenSize = (): boolean => document.body.offsetWidth < 480;

  /** Key up event emitter of the default source filter input. */
  sourceFilterKeyup = new EventEmitter<string>();

  /** Key up event emitter of the default target filter input. */
  targetFilterKeyup = new EventEmitter<string>();

  /** Whether some source items are available. */
  isSourceItemsAvailable = false;

  /** Whether some target items are available. */
  isTargetItemsAvailable = false;

  /** Whether some source items are selected. */
  isSomeSourceItemSelected = false;

  /** Whether some target items are selected. */
  isSomeTargetItemSelected = false;

  /** Whether to show source Deselect All button */
  showSourceDeselectAllButton = false;

  /** Whether to show target Deselect All button */
  showTargetDeselectAllButton = false;

  /** Whether to show source/target sort button */
  @Input() showSort = false;

  /** Wheter the current source selection including current target items count would exceed the target items max limit. */
  wouldSourceSelectionExceedTargetItemsMaxLimit = false;

  CoreDragState: CoreDragInstance = {
    state: CoreDragState.Idle,
    source: 'none',
    draggedItems: [],
  };

  /** Hides the source/target filter input. */
  get disableFilter() : boolean {
    return typeof this.showFilter === 'boolean' && !this.showFilter;
  }

  /** Template for the source/target list filter. */
  @Input() customFilterTemplate: TemplateRef<any>;

  /** whether the item is multiline */
  @Input() isMultilineItem = false;

  /** List of source items. */
  get source(): any[] {
    return this._source;
  }

  @Input() set source(value: any[]) {
    this._source = value;
    this.picklistService.setSourceItems(value, false);
    this.picklistService.selectSourceItems(this._sourceSelected);
  }

  /** List of source selected items. */
  get sourceSelected(): any[] {
    return this._sourceSelected;
  }

  @Input() set sourceSelected(value: any[]) {
    this._sourceSelected = value;
    this.picklistService.selectSourceItems(value);
  }

  _targetItemsMaxLimit: number;

  /**
   * Defines a specified maximum limit for target items.
   * If not defined, no limit is applied.
   */
  @Input() set targetItemsMaxLimit(value: number) {
    this._targetItemsMaxLimit = value;
    this.picklistService.setTargetItemsMaxLimit(value);
  }

  /** List of target items. */
  get target(): any[] {
    return this._target;
  }

  @Input() set target(value: any[]) {
    this._target = value;
    this.picklistService.setTargetItems(value, false);
    this.picklistService.selectTargetItems(this._targetSelected);
  }

  /** List of target selected items. */
  get targetSelected(): any[] {
    return this._targetSelected;
  }

  @Input() set targetSelected(value: any[]) {
    this._targetSelected = value;
    this.picklistService.selectTargetItems(value);
  }

  /**
   * Property that is displayed in the source or target list, depending if a source or target item template have been defined.
   * Ignored if a source or target item template has been specified.
   */
  @Input() displayProperty: string;

  /** Template for items in the source list. This template definition is independent from the target template. */
  @Input() sourceItemTemplate: TemplateRef<any>;

  /** Template for items in the target list. This template definition is independent from the source template. */
  @Input() targetItemTemplate: TemplateRef<any>;

  /** Defines whether elements are copied or moved from the list of source items to the list of target items. */
  @Input() mode: BciCorePicklistMode = 'move';

  @Input() showFilter: boolean | string | BciCorePicklistStringFilter | Observable<BciCorePicklistFilter> = false;
  /** Header for the source list filter. */
  @Input() sourceFilterHeader: string;

  /** Header for the target list filter. */
  @Input() targetFilterHeader: string;

  /** Return the current number of source items in the list. */
  getSourceItemCount(): number {
    return this.sourceItemCount;
  }

  /**
   * Sort behavior of the source list. If a comparison function is specified, it is evaluated. If using a comparison function return
   * -1 if the first element should appear below the second element, 0 if both elements are equal, or 1 if the second element should
   * appear below the first element.
   */
  sourceSorting: BciCorePicklistSorting = 'none'; // TODO Restore: Use 'none' as default

  /** Return the current number of target items in the list. */
  getTargetItemCount(): number {
    return this.targetItemCount;
  }

  /**
   * Sort behavior of the target list. If a comparison function is specified, it is evaluated. If using a comparison function return
   * -1 if the first element should appear below the second element, 0 if both elements are equal, or 1 if the second element should
   * appear below the first element.
   */
  targetSorting: BciCorePicklistSorting = 'none'; // TODO Restore: Use 'none' as default

  /** Whether list item is multiline. */
  get isMultiLineElement(): boolean {
    return typeof this.isMultilineItem === 'boolean' && this.isMultilineItem === true; // TODO Restore: return this.targetSorting === 'manual';
  }

  /** Whether self dropping on target must be disabled. */
  get dropSelfTargetDisabled(): boolean {
    return this.showSort && this.picklistService.isTargetFilterActive();
  }

  /** Whether to disable the filter at the source list based on no of items */
  get isSourceFilterActive(): boolean {
    return this.picklistService.isSourceFilterActive();
  }
  
  /** Whether to disable the filter at the target list based on no of items */
  get isTargetFilterActive(): boolean {
    return this.picklistService.isTargetFilterActive();
  }

  /** Propagates a copy of the current source list on change. */
  @Output() sourceChange: EventEmitter<any[]> = new EventEmitter();

  /** Propagates a copy of the current target list on change. */
  @Output() targetChange: EventEmitter<any[]> = new EventEmitter();

  /** Propagates a copy of the current source selected list on change. */
  @Output() sourceSelectedChange: EventEmitter<any[]> = new EventEmitter();

  /** Propagates a copy of the current target selected list on change. */
  @Output() targetSelectedChange: EventEmitter<any[]> = new EventEmitter();

  /** Propagates a copy of the current target selected list on change. */
  @Output() noOfItemsChangedWithAction: EventEmitter<any[]> = new EventEmitter();

  /** Used for component-wide unsubscription on destroy. */
  private ngUnsubscribe = new Subject<void>();

  /** Used for unsubscribing from key up events coming form the source filter text box. */
  private sourceFilterKeyUpUnsubscribe = new Subject<void>();

  /** Used for unsubscribing from key up events coming form the target filter text box. */
  private targetFilterKeyUpUnsubscribe = new Subject<void>();

  private _source: any[] = [];
  private _target: any[] = [];

  private _sourceSelected: any[] = [];
  private _targetSelected: any[] = [];

  /** Number of currently available source items. */
  private sourceItemCount: number;

  /** Number of currently available target items. */
  private targetItemCount: number;

  constructor(
    public picklistIntl: BciCorePicklistIntl,
    private picklistService: BciCorePicklistService,
    private changeDetector: ChangeDetectorRef
  ) {}

  ngOnInit(): void {
    this.showSort && (this.sourceSorting = 'asc');
    this.showSort && (this.targetSorting = 'asc');
    this.picklistService.initialize(
      this.source,
      this.target,
      this.sourceSorting,
      this.targetSorting,
      this.displayProperty,
      this.mode
    );
    this.initSourceFilter(this.showFilter);
    this.initTargetFilter(this.showFilter);

    // Subscribe after filters have been applied
    this.subscribeToPicklistService();

    this.picklistIntl.changes.pipe(takeUntil(this.ngUnsubscribe)).subscribe(() => this.changeDetector.detectChanges());

    const screenSizeChanged$ = fromEvent(window, 'resize').pipe(map(this.checkScreenSize));
    this.isMobile$ = screenSizeChanged$.pipe(startWith(this.checkScreenSize()));
  }

  ngOnDestroy(): void {
    this.ngUnsubscribe.next();
    this.ngUnsubscribe.complete();
  }

  /** Move selected source items to target list. */
  onAddSelected(): void {
    this.picklistService.addSelection();
  }

  /** Move all source items to target list. */
  onAddAll(): void {
    this.picklistService.addAll();
  }

  /** Move selected target items to source list. */
  onRemoveSelected(): void {
    this.picklistService.removeSelection();
  }

  /** Move all target items to source list. */
  onRemoveAll(): void {
    this.picklistService.removeAll();
  }

  /** True if no source items available or current count of source items including current count of target items reached target items max limit if set*/
  isAddAllButtonDisabled(): boolean {
    return !this.isSourceItemsAvailable || this.sourceItemCount + this.targetItemCount > this._targetItemsMaxLimit;
  }

  /** Check all source items. */
  onSelectAll(responsibility: BciCorePicklistResponsibility): void {
    switch (responsibility) {
      case 'source': {
        this.picklistService.selectAllSourceItems();
        break;
      }
      case 'target': {
        this.picklistService.selectAllTargetItems();
        break;
      }
    }
  }

  /** Uncheck all source items. */
  onDeselectAll(responsibility: BciCorePicklistResponsibility): void {
    switch (responsibility) {
      case 'source': {
        this.picklistService.deselectAllSourceItems();
        break;
      }
      case 'target': {
        this.picklistService.deselectAllTargetItems();
        break;
      }
    }
  }

  setSourceSorting(event) {
    if (event.value === 'asc' || event.value === 'desc') {
      this.sourceSorting = event.value;
    } else {
      this.sourceSorting === 'none';
    }
    this.picklistService.setSourceSortBehavior(this.sourceSorting);
  }

  setTargetSorting(event) {
    if (event.value === 'asc' || event.value === 'desc') {
      this.targetSorting = event.value;
    } else {
      this.targetSorting === 'none';
    }
    this.picklistService.setTargetSortBehavior(this.targetSorting);
  }

  /**
   * Initializes the source filter.
   * @param sourceFilter Filter is disabled when set to false.
   */
  private initSourceFilter(
    sourceFilter: boolean | string | BciCorePicklistStringFilter | Observable<BciCorePicklistFilter>
  ): void {
    if (typeof sourceFilter === 'boolean') {
      if (sourceFilter) {
        this.picklistService.setSourceFilter('');
        this.sourceFilterKeyUpUnsubscribe.next();
        this.sourceFilterKeyup
          .pipe(takeUntil(this.ngUnsubscribe), takeUntil(this.sourceFilterKeyUpUnsubscribe))
          .subscribe((filter) => this.picklistService.setSourceFilter(filter));
      } else {
        this.picklistService.setSourceFilter(false);
      }
    } else if (typeof sourceFilter === 'string') {
      this.picklistService.setSourceFilter(sourceFilter);
      this.sourceFilterKeyUpUnsubscribe.next();
      this.sourceFilterKeyup
        .pipe(takeUntil(this.ngUnsubscribe), takeUntil(this.sourceFilterKeyUpUnsubscribe))
        .subscribe((filter) => this.picklistService.setSourceFilter(filter));
    } else if (sourceFilter instanceof Observable) {
      this.picklistService.setSourceFilter(sourceFilter);
    } else {
      this.sourceFilterKeyUpUnsubscribe.next();
      this.sourceFilterKeyup
        .pipe(takeUntil(this.ngUnsubscribe), takeUntil(this.sourceFilterKeyUpUnsubscribe))
        .subscribe((filter) =>
          this.picklistService.setSourceFilter((item: any): boolean => {
            return sourceFilter(item, filter);
          })
        );
    }
  }

  /**
   * Initializes the target filter.
   * @param targetFilter Filter is disabled when set to false.
   */
  private initTargetFilter(
    targetFilter: boolean | string | BciCorePicklistStringFilter | Observable<BciCorePicklistFilter>
  ): void {
    if (typeof targetFilter === 'boolean') {
      if (targetFilter) {
        this.picklistService.setTargetFilter('');
        this.targetFilterKeyUpUnsubscribe.next();
        this.targetFilterKeyup
          .pipe(takeUntil(this.ngUnsubscribe), takeUntil(this.targetFilterKeyUpUnsubscribe))
          .subscribe((filter) => this.picklistService.setTargetFilter(filter));
      } else {
        this.picklistService.setTargetFilter(false);
      }
    } else if (typeof targetFilter === 'string') {
      this.picklistService.setTargetFilter(targetFilter);
      this.targetFilterKeyUpUnsubscribe.next();
      this.targetFilterKeyup
        .pipe(takeUntil(this.ngUnsubscribe), takeUntil(this.targetFilterKeyUpUnsubscribe))
        .subscribe((filter) => this.picklistService.setTargetFilter(filter));
    } else if (targetFilter instanceof Observable) {
      this.picklistService.setTargetFilter(targetFilter);
    } else {
      this.targetFilterKeyUpUnsubscribe.next();
      this.targetFilterKeyup
        .pipe(takeUntil(this.ngUnsubscribe), takeUntil(this.targetFilterKeyUpUnsubscribe))
        .subscribe((filter) =>
          this.picklistService.setTargetFilter((item: any): boolean => {
            return targetFilter(item, filter);
          })
        );
    }
  }

  /** Subscribes filtered and sorted source and target lists. */
  private subscribeToPicklistService(): void {
    this.picklistService.processedSourceItems.pipe(takeUntil(this.ngUnsubscribe)).subscribe((items) => {
      this.sourceItemCount = items.length;
      this.isSourceItemsAvailable = items && items.length > 0;
      if (
        this.picklistService.getOriginalSourceItems().length > 0 &&
        items.length <= this.picklistService.getOriginalSourceItems().length &&
        items.length > 0
      ) {
        this.showSourceDeselectAllButton = items.filter((x) => x.selected == true).length == items.length;
      } else {
        this.showSourceDeselectAllButton = false;
      }
    });

    this.picklistService.processedTargetItems.pipe(takeUntil(this.ngUnsubscribe)).subscribe((items) => {
      this.targetItemCount = items.length;
      this.isTargetItemsAvailable = items && items.length > 0;
      if (
        this.picklistService.getOriginalTargetItems().length > 0 &&
        items.length <= this.picklistService.getOriginalTargetItems().length &&
        items.length > 0
      ) {
        this.showTargetDeselectAllButton = items.filter((x) => x.selected == true).length == items.length;
      } else {
        this.showTargetDeselectAllButton = false;
      }
    });

    this.picklistService.CoreSelectionState$.pipe(takeUntil(this.ngUnsubscribe)).subscribe((state) => {
      this.isSomeSourceItemSelected = state.sourceItemsSelected;
      this.isSomeTargetItemSelected = state.targetItemsSelected;
    });

    this.picklistService.sourceItemsChange.pipe(takeUntil(this.ngUnsubscribe)).subscribe((items) => {
      items.length == 0 && (this.showSourceDeselectAllButton = false);
      this.sourceChange.emit(items);
    });

    this.picklistService.targetItemsChange.pipe(takeUntil(this.ngUnsubscribe)).subscribe((items) => {
      items.length == 0 && (this.showTargetDeselectAllButton = false);
      this.targetChange.emit(items);
    });

    this.picklistService.sourceSelectedItemsChange.pipe(takeUntil(this.ngUnsubscribe)).subscribe((items) => {
      if (
        this.picklistService.getOriginalSourceItems().length > 0 &&
        items.length <= this.picklistService.getOriginalSourceItems().length &&
        items.length > 0
      ) {
        this.showSourceDeselectAllButton = this.sourceItemCount === items.length;
      } else if (items.length === 0) {
        this.showSourceDeselectAllButton = false;
      }
      this.sourceSelectedChange.emit(items);
    });

    this.picklistService.targetSelectedItemsChange.pipe(takeUntil(this.ngUnsubscribe)).subscribe((items) => {
      if (
        this.picklistService.getOriginalTargetItems().length > 0 &&
        items.length <= this.picklistService.getOriginalTargetItems().length &&
        items.length > 0
      ) {
        this.showTargetDeselectAllButton = this.targetItemCount === items.length;
      } else if (items.length === 0) {
        this.showTargetDeselectAllButton = false;
      }
      this.targetSelectedChange.emit(items);
    });

    this.picklistService.noOfItemsChanged.pipe(takeUntil(this.ngUnsubscribe)).subscribe((itemschanged) => {
      this.noOfItemsChangedWithAction.emit(itemschanged);
    });

    combineLatest([this.picklistService.processedTargetItems, this.picklistService.sourceSelectedItemsChange])
      .pipe(takeUntil(this.ngUnsubscribe))
      .subscribe(([targetItems, sourceSelection]) => {
        this.wouldSourceSelectionExceedTargetItemsMaxLimit =
          this._targetItemsMaxLimit < targetItems.length + sourceSelection.length;
      });
  }
}
