import type { BoxModel } from 'css-box-model';
import { combine, transforms, transitions } from '../../animation';
import type { DraggableDimension } from '../../types';
import type {
  DraggingStyle,
  NotDraggingStyle,
  ZIndexOptions,
  DropAnimation,
  SecondaryMapProps,
  DraggingMapProps,
  DraggableStyle,
  MappedProps,
} from './draggable-types';

export const zIndexOptions: ZIndexOptions = {
  dragging: 5000,
  dropAnimating: 4500,
};

const getDraggingTransition = (
  shouldAnimateDragMovement: boolean,
  dropping?: DropAnimation | null,
): string => {
  if (dropping) {
    return transitions.drop(dropping.duration);
  }
  if (shouldAnimateDragMovement) {
    return transitions.snap;
  }
  return transitions.fluid;
};

const getDraggingOpacity = (
  isCombining: boolean,
  isDropAnimating: boolean,
): number | undefined => {
  // if not combining: no not impact opacity
  if (!isCombining) {
    return undefined;
  }

  return isDropAnimating ? combine.opacity.drop : combine.opacity.combining;
};

const getShouldDraggingAnimate = (dragging: DraggingMapProps): boolean => {
  if (dragging.forceShouldAnimate != null) {
    return dragging.forceShouldAnimate;
  }
  return dragging.mode === 'SNAP';
};

function getDraggingStyle(dragging: DraggingMapProps): DraggingStyle {
  const dimension: DraggableDimension = dragging.dimension;
  const box: BoxModel = dimension.client;
  const { offset, combineWith, dropping } = dragging;

  const isCombining = Boolean(combineWith);

  const shouldAnimate: boolean = getShouldDraggingAnimate(dragging);
  const isDropAnimating = Boolean(dropping);

  const transform: string | undefined = isDropAnimating
    ? transforms.drop(offset, isCombining)
    : transforms.moveTo(offset);

  const style: DraggingStyle = {
    // ## Placement
    position: 'fixed',
    // As we are applying the margins we need to align to the start of the marginBox
    top: box.marginBox.top,
    left: box.marginBox.left,

    // ## Sizing
    // Locking these down as pulling the node out of the DOM could cause it to change size
    boxSizing: 'border-box',
    width: box.borderBox.width,
    height: box.borderBox.height,

    // ## Movement
    // Opting out of the standard css transition for the dragging item
    transition: getDraggingTransition(shouldAnimate, dropping),
    transform,
    opacity: getDraggingOpacity(isCombining, isDropAnimating),
    // ## Layering
    zIndex: isDropAnimating
      ? zIndexOptions.dropAnimating
      : zIndexOptions.dragging,

    // ## Blocking any pointer events on the dragging or dropping item
    // global styles on cover while dragging
    pointerEvents: 'none',
  };
  return style;
}

function getSecondaryStyle(secondary: SecondaryMapProps): NotDraggingStyle {
  return {
    transform: transforms.moveTo(secondary.offset),
    // transition style is applied in the head
    transition: secondary.shouldAnimateDisplacement ? undefined : 'none',
  };
}

export default function getStyle(mapped: MappedProps): DraggableStyle {
  return mapped.type === 'DRAGGING'
    ? getDraggingStyle(mapped)
    : getSecondaryStyle(mapped);
}