/* eslint-disable no-param-reassign */
import { ReactZoomPanPinchContext } from "../../models";
import { handleCancelAnimation } from "../animations/animations.utils";
import { handleAlignToScaleBounds } from "../zoom/zoom.logic";
import {
  calculatePinchZoom,
  calculateTouchMidPoint,
  getTouchDistance,
} from "./pinch.utils";
import {
  getMouseBoundedPosition,
  handleCalculateBounds,
} from "../bounds/bounds.utils";
import { handleCalculateZoomPositions } from "../zoom/zoom.utils";
import { getPaddingValue } from "core/pan/panning.utils";

const getTouchCenter = (event: TouchEvent) => {
  let totalX = 0;
  let totalY = 0;
  // Sum up the positions of all touches
  for (let i = 0; i < 2; i += 1) {
    totalX += event.touches[i].clientX;
    totalY += event.touches[i].clientY;
  }

  // Calculate the average position
  const x = totalX / 2;
  const y = totalY / 2;

  return { x, y };
};

export const handlePinchStart = (
  contextInstance: ReactZoomPanPinchContext,
  event: TouchEvent,
): void => {
  const distance = getTouchDistance(event);
  contextInstance.pinchStartDistance = distance;
  contextInstance.lastDistance = distance;
  contextInstance.pinchStartScale = contextInstance.state.scale;
  contextInstance.isPanning = false;
  contextInstance.isPinching = true;

  contextInstance.pinchPreviousCenter = getTouchCenter(event);

  handleCancelAnimation(contextInstance);
};

export const handlePinchZoom = (
  contextInstance: ReactZoomPanPinchContext,
  event: TouchEvent,
): void => {
  const {
    contentComponent,
    pinchStartDistance,
    wrapperComponent,
    pinchPreviousCenter,
  } = contextInstance;
  const { scale } = contextInstance.state;
  const {
    limitToBounds,
    centerZoomedOut,
    zoomAnimation,
    autoAlignment,
    pinch,
    panning,
  } = contextInstance.setup;
  const { disabled, size } = zoomAnimation;
  const { allowPanning } = pinch;

  // if one finger starts from outside of wrapper
  if (pinchStartDistance === null || !contentComponent) return;
  const midPoint = calculateTouchMidPoint(event, scale, contentComponent);
  // if touches goes off of the wrapper element
  if (!Number.isFinite(midPoint.x) || !Number.isFinite(midPoint.y)) return;
  const currentDistance = getTouchDistance(event);
  const newScale = calculatePinchZoom(contextInstance, currentDistance);

  const center = getTouchCenter(event);

  // pan should be scale invariant.
  const scaleDiff = scale / newScale;

  const panX = (center.x - (pinchPreviousCenter?.x || 0)) * scaleDiff;
  const panY = (center.y - (pinchPreviousCenter?.y || 0)) * scaleDiff;

  if (newScale === scale && panX === 0 && panY === 0) return;

  contextInstance.pinchPreviousCenter = center;

  const bounds = handleCalculateBounds(contextInstance, newScale);

  const isPaddingDisabled = disabled || size === 0 || centerZoomedOut;
  const isLimitedToBounds = limitToBounds && isPaddingDisabled;
  const { x, y } = handleCalculateZoomPositions(
    contextInstance,
    midPoint.x,
    midPoint.y,
    newScale,
    bounds,
    isLimitedToBounds,
  );
  contextInstance.pinchMidpoint = midPoint;
  contextInstance.lastDistance = currentDistance;

  if (panning.disabled || !allowPanning) {
    contextInstance.setState(newScale, x, y);
  } else {
    const { sizeX, sizeY } = autoAlignment;
    const paddingValueX = getPaddingValue(contextInstance, sizeX, newScale);
    const paddingValueY = getPaddingValue(contextInstance, sizeY, newScale);

    const newPositionX = x + panX;
    const newPositionY = y + panY;
    const { x: finalX, y: finalY } = getMouseBoundedPosition(
      newPositionX,
      newPositionY,
      bounds,
      limitToBounds,
      paddingValueX,
      paddingValueY,
      wrapperComponent,
    );
    contextInstance.setState(newScale, finalX, finalY);
  }
};

export const handlePinchStop = (
  contextInstance: ReactZoomPanPinchContext,
): void => {
  const { pinchMidpoint } = contextInstance;
  contextInstance.velocity = null;
  contextInstance.lastDistance = null;
  contextInstance.pinchMidpoint = null;
  contextInstance.pinchStartScale = null;
  contextInstance.pinchStartDistance = null;
  contextInstance.isPinching = false;
  handleAlignToScaleBounds(contextInstance, pinchMidpoint?.x, pinchMidpoint?.y);
};
