import type { Position } from 'css-box-model';
import type {
  DroppableDimension,
  DraggableDimension,
  DraggableDimensionMap,
  DragImpact,
  Viewport,
  LiftEffect,
} from '../../../types';
import type { PublicResult } from '../move-in-direction-types';
import getDraggablesInsideDroppable from '../../get-draggables-inside-droppable';
import moveToNextCombine from './move-to-next-combine';
import moveToNextIndex from './move-to-next-index';
import isHomeOf from '../../droppable/is-home-of';
import getPageBorderBoxCenter from '../../get-center-from-impact/get-page-border-box-center';
import speculativelyIncrease from '../../update-displacement-visibility/speculatively-increase';
import getClientFromPageBorderBoxCenter from '../../get-center-from-impact/get-client-border-box-center/get-client-from-page-border-box-center';
import { subtract } from '../../position';
import isTotallyVisibleInNewLocation from './is-totally-visible-in-new-location';

interface Args {
  isMovingForward: boolean;
  draggable: DraggableDimension;
  destination: DroppableDimension;
  draggables: DraggableDimensionMap;
  previousImpact: DragImpact;
  viewport: Viewport;
  previousClientSelection: Position;
  previousPageBorderBoxCenter: Position;
  afterCritical: LiftEffect;
}

export default ({
  isMovingForward,
  draggable,
  destination,
  draggables,
  previousImpact,
  viewport,
  previousPageBorderBoxCenter,
  previousClientSelection,
  afterCritical,
}: Args): PublicResult | null => {
  if (!destination.isEnabled) {
    return null;
  }

  const insideDestination: DraggableDimension[] = getDraggablesInsideDroppable(
    destination.descriptor.id,
    draggables,
  );
  const isInHomeList: boolean = isHomeOf(draggable, destination);

  const impact: DragImpact | null =
    moveToNextCombine({
      isMovingForward,
      draggable,
      destination,
      insideDestination,
      previousImpact,
    }) ||
    moveToNextIndex({
      isMovingForward,
      isInHomeList,
      draggable,
      draggables,
      destination,
      insideDestination,
      previousImpact,
      viewport,
      afterCritical,
    });

  if (!impact) {
    return null;
  }

  const pageBorderBoxCenter: Position = getPageBorderBoxCenter({
    impact,
    draggable,
    droppable: destination,
    draggables,
    afterCritical,
  });

  const isVisibleInNewLocation: boolean = isTotallyVisibleInNewLocation({
    draggable,
    destination,
    newPageBorderBoxCenter: pageBorderBoxCenter,
    viewport: viewport.frame,
    // already taken into account by getPageBorderBoxCenter
    withDroppableDisplacement: false,
    // we only care about it being visible relative to the main axis
    // this is important with dynamic changes as scroll bar and toggle
    // on the cross axis during a drag
    onlyOnMainAxis: true,
  });

  if (isVisibleInNewLocation) {
    // using the client center as the selection point
    const clientSelection: Position = getClientFromPageBorderBoxCenter({
      pageBorderBoxCenter,
      draggable,
      viewport,
    });
    return {
      clientSelection,
      impact,
      scrollJumpRequest: null,
    };
  }

  const distance: Position = subtract(
    pageBorderBoxCenter,
    previousPageBorderBoxCenter,
  );

  const cautious: DragImpact = speculativelyIncrease({
    impact,
    viewport,
    destination,
    draggables,
    maxScrollChange: distance,
  });

  return {
    clientSelection: previousClientSelection,
    impact: cautious,
    scrollJumpRequest: distance,
  };
};