/*
 * Copyright (C) 2023 Robert Bosch GmbH Copyright (C) 2023 Robert Bosch Manufacturing Solutions GmbH, Germany. All rights reserved.
 */

import { CdkDragDrop, CdkDragEnd, CdkDragStart } from '@angular/cdk/drag-drop';
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output, TemplateRef, ViewChild } from '@angular/core';
import { MatSelectionList } from '@angular/material/list';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { BciCorePicklistIntl } from '../bci-core-picklist-intl';

import { CoreDragInstance, CoreDragState, BciCorePicklistItem } from '../bci-core-picklist.model';
import { BciCorePicklistService } from '../bci-core-picklist.service';
import { BciCorePicklistResponsibility } from '../bci-core-picklist.types';

/** A list of items that can be selected and dragged/dropped. */
@Component({
  selector: 'bci-core-drag-drop-list',
  templateUrl: './bci-core-drag-drop-list.component.html',
  styleUrls: ['./bci-core-drag-drop-list.component.scss'],
})
export class BciCoreDragDropListComponent implements OnInit, OnDestroy {
  /** Items received from the {@link PicklistService}, depending on the list's responsibility. */
  items: BciCorePicklistItem[] = [];

  /** Whether the items list acts as source or target. */
  @Input() responsibility: BciCorePicklistResponsibility;

  /** Optional item template. If undefined the items are displayed using their string representation. */
  @Input() itemTemplate: TemplateRef<any>;

  /** Whether items can be manually repositioned via drag and drop. */
  @Input() manualSorting = false;

  /** Placeholder that is shown the list is empty. */
  @Input() dropOverlayPlaceholder: string;

  /** Whether drop on self must be disabled. */
  @Input() dropSelfDisabled = false;

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

  @Input() CoreDragInstance: CoreDragInstance;

  @Output() CoreDragInstanceChange = new EventEmitter<CoreDragInstance>();

  // eslint-disable-next-line @angular-eslint/no-output-on-prefix
  @Output() onClickofAction = new EventEmitter<string>();

  private ngUnsubscribe = new Subject<void>();

  private readonly numPreviewItems = 4;

  @ViewChild(MatSelectionList) private selectionList: MatSelectionList;

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

  /** Whether the drop area must be highlighted. */
  get highlightDropArea(): boolean {
    return (
      (this.responsibility === 'target' &&
        this.CoreDragInstance.source === 'source' &&
        this.CoreDragInstance.state === CoreDragState.Dragging) ||
      (this.responsibility === 'target' &&
        this.CoreDragInstance.source === 'target' &&
        this.CoreDragInstance.state === CoreDragState.Dragging &&
        this.manualSorting &&
        !this.dropSelfDisabled) ||
      (this.responsibility === 'source' &&
        this.CoreDragInstance.source === 'target' &&
        this.CoreDragInstance.state === CoreDragState.Dragging)
    );
  }

  get isSourceList(): boolean {
    return this.responsibility === 'source';
  }

  /**
   * Items of the current {@link DragDropListComponent#CoreDragInstance drag state's} if available.
   */
  selectedPreviewItems(): BciCorePicklistItem[] {
    return this.CoreDragInstance.draggedItems.filter((item, index) => index < this.numPreviewItems);
  }

  remainingPreviewItems(): number {
    return this.CoreDragInstance.draggedItems.length - this.numPreviewItems;
  }

  ngOnInit(): void {
    switch (this.responsibility) {
      case 'source': {
        this.picklistService.processedSourceItems.pipe(takeUntil(this.ngUnsubscribe)).subscribe((list) => {
          if (this.selectionList && this.selectionList.selectedOptions) {
            // since items list will reset, selected items should also reset
            this.selectionList.selectedOptions.clear();
          }
          this.items.length = 0;
          this.items.push(...list);
        });
        break;
      }
      case 'target': {
        this.picklistService.processedTargetItems.pipe(takeUntil(this.ngUnsubscribe)).subscribe((list) => {
          if (this.selectionList && this.selectionList.selectedOptions) {
            // since items list will reset, selected items should also reset
            this.selectionList.selectedOptions.clear();
          }
          this.items.length = 0;
          this.items.push(...list);
        });
        break;
      }
    }
  }

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

  /**
   * Returns a context depending on the type of the value.
   * @param value Item context
   */
  getItemContext(value: any): any {
    return typeof value === 'object' ? value : { value: value };
  }

  /**
   * Called when the item selection changes.
   * @param selected
   * @param item Selected item.
   */
  onSelectionChange(selected: boolean, item: BciCorePicklistItem,): void {
    item.selected = selected;
    
    switch (this.responsibility) {
      case 'source': {
        this.picklistService.updateSourceSelection();
        break;
      }
      case 'target': {
        this.picklistService.updateTargetSelection();
        break;
      }
    }
  }

  /**
   * Called when an item has been double clicked.
   * @param item Selected item that has been double clicked.
   */
  onDoubleClick(item: BciCorePicklistItem): void {
    switch (this.responsibility) {
      case 'source': {
        this.picklistService.addItems(item);
        break;
      }
      case 'target': {
        this.picklistService.removeItems(0, item);
        break;
      }
    }
  }

  onDrop(event: CdkDragDrop<BciCorePicklistItem[]>): void {
    const selectedItems = event.previousContainer.data.filter((elem) => elem.selected);
    if (selectedItems.length >= 1) {
      selectedItems[0].selected = false;
    }

    // NOTE: Index is needed due to the difference between item array and list with placeholder.
    const index = event.previousIndex < event.currentIndex ? event.currentIndex + 1 : event.currentIndex;

    if (
      this.responsibility === 'target' &&
      event.previousContainer === event.container &&
      this.manualSorting &&
      !this.dropSelfDisabled && event.isPointerOverContainer
    ) {
      // Reposition on target
      this.picklistService.repositionTargetItems(index, ...selectedItems);
    } else if (this.responsibility === 'target' && event.previousContainer !== event.container) {
      // Drag to target
      this.picklistService.insertItems(event.currentIndex, ...selectedItems);
    } else if ((this.responsibility === 'source' && event.previousContainer !== event.container) || (this.responsibility === 'target' && event.previousContainer === event.container && !event.isPointerOverContainer)) {
      // Drag to source
      // calculate the index where to insert the item
      const sourceIndex = event.currentIndex > 2 ? event.currentIndex - 1 : event.currentIndex;
      this.picklistService.removeItems(sourceIndex, ...selectedItems);
    } else if (this.responsibility === 'source' && event.previousContainer === event.container && !event.isPointerOverContainer) {
      // Drag from source to target
      this.picklistService.insertItems(index, ...selectedItems);
    }
  }

  onDragStarted(event: CdkDragStart): void {
    event.source.data.selected = true;
    event.source.dropContainer.data.forEach((item) => {
      item.dragging = item.selected;
    });
    this.CoreDragInstance = {
      source: this.responsibility,
      state: CoreDragState.Dragging,
      draggedItems: event.source.dropContainer.data.filter((item) => item.dragging === true),
    };
    this.CoreDragInstanceChange.emit(this.CoreDragInstance);
  }

  onDragEnded(event: CdkDragEnd): void {
    event.source.dropContainer.data.forEach((item) => {
      item.dragging = false;
    });
    this.CoreDragInstance = {
      source: 'none',
      state: CoreDragState.Idle,
      draggedItems: [],
    };
    this.CoreDragInstanceChange.emit(this.CoreDragInstance);
  }
}
