import { DragNDropValueObject } from 'common/models/elements/label-interfaces';
import { DropListComponent } from 'common/components/input-elements/drop-list/drop-list.component';

export class DropLogic {
  static createDropListMock(dropListComp: DropListComponent): DropListMock {
    return {
      id: dropListComp.elementModel.id,
      value: [...dropListComp.elementFormControl.value],
      isSortList: dropListComp.elementModel.isSortList,
      onlyOneItem: dropListComp.elementModel.onlyOneItem,
      connectedTo: [...dropListComp.elementModel.connectedTo],
      copyOnDrop: dropListComp.elementModel.copyOnDrop,
      allowReplacement: dropListComp.elementModel.allowReplacement
    };
  }

  static createDropListMocks(source: { [id: string]: DropListComponent }): { [id: string]: DropListMock } {
    return Object.fromEntries(
      Object.entries(source).map(([key, value]) => [
        key,
        DropLogic.createDropListMock(value)
      ])
    );
  }

  static isDropAllowed(draggedItem: DragNDropValueObject,
                       sourceListID: string,
                       targetListID: string,
                       allLists: { [id: string]: DropListMock },
                       ignoreConnection: boolean = false): boolean {
    const sourceList = allLists[sourceListID];
    const targetList = allLists[targetListID];
    return DropLogic.checkIsSourceList(sourceList, targetList) &&
      DropLogic.checkConnected(sourceList, targetList, ignoreConnection) &&
      DropLogic.checkOnlyOneItem(draggedItem, targetList, allLists) &&
      DropLogic.checkAddForeignItemToCopyList(draggedItem, targetList);
  }

  /* Only allow drops in other lists, except for sortlists. */
  private static checkIsSourceList(sourceList: DropListMock, targetList: DropListMock): boolean {
    return (sourceList.id === targetList.id && sourceList.isSortList) ||
    sourceList.id !== targetList.id;
  }

  /* Check list connection, sortlist is an exception since source and target can be the same. */
  private static checkConnected(sourceList: DropListMock,
                                targetList: DropListMock, ignoreConnection: boolean = false): boolean {
    return ignoreConnection ||
      (sourceList.id === targetList.id && sourceList.isSortList) ||
      sourceList.connectedTo.includes(targetList.id);
  }

  // ### Only One Item ###

  /* Return false, when drop is not allowed */
  private static checkOnlyOneItem(draggedItem: DragNDropValueObject, targetList: DropListMock,
                                  allLists: { [id: string]: DropListMock }): boolean {
    return !(targetList.onlyOneItem &&
      targetList.value.length > 0 &&
      !DropLogic.isReplace(draggedItem, targetList, allLists));
  }

  static isReplace(draggedItem: DragNDropValueObject, targetList: DropListMock,
                   allLists: { [id: string]: DropListMock }): boolean {
    if (!(targetList.onlyOneItem && targetList.value.length === 1 && targetList.allowReplacement)) {
      return false;
    }
    // Item is already in it's origin
    if (targetList.value[0].originListID === targetList.id) {
      return false;
    }

    const rest = targetList.value.splice(0, 1, draggedItem)[0];
    return DropLogic.isDropAllowed(
      rest,
      targetList.id,
      allLists[rest.originListID].id,
      allLists,
      true
    );
  }

  // ### Copy List ###

  static isPutBack(draggedItem: DragNDropValueObject, targetList: DropListMock): boolean {
    return targetList.copyOnDrop && draggedItem.originListID === targetList.id;
  }

  /* Don't allow moving item into copy list that does not originate from there. */
  private static checkAddForeignItemToCopyList(draggedItem: DragNDropValueObject | undefined,
                                               targetList: DropListMock): boolean {
    return !(targetList.copyOnDrop && draggedItem?.originListID !== targetList.id);
  }
}

export interface DropListMock {
  id: string;
  value: DragNDropValueObject[];
  isSortList: boolean;
  onlyOneItem: boolean;
  connectedTo: string[];
  copyOnDrop: boolean;
  allowReplacement: boolean;
}