import { getSecondsFromZero } from '@suno/studiokit/timeMapping';

import { getDerivedTiming } from './selectors';
import { StudioProjectState } from './types';

export const DRAG_THRESHOLD = 5;

export type ClickDragMouseDownHandler = (downParams: {
  downTrackId: string | null;
  downEvent: MouseEvent;
  downSeconds: number;
  downBeats: number;
  downCanvasRelativePx: number;
  downContentRelativePx: number;
}) => {
  onMouseMove?: (moveParams: {
    moveTrackId: string | null;
    moveEvent: MouseEvent;
    moveSeconds: number;
    moveBeats: number;
    moveCanvasRelativePx: number;
    moveContentRelativePx: number;
    isDrag: boolean;
  }) => void;
  onMouseUp?: (upParams: {
    upTrackId: string | null;
    upEvent: MouseEvent;
    upSeconds: number;
    upBeats: number;
    upCanvasRelativePx: number;
    upContentRelativePx: number;
    isDrag: boolean;
  }) => void;
};

export default function handleClickDrag(
  scrollXRef: React.RefObject<number>,
  getWrapperRect: () => DOMRect,
  clientXToBeats: (x: number) => number,
  clientYToTrackId: (y: number) => string | null,
  stateRef: React.RefObject<StudioProjectState>,
  onMouseDown: ClickDragMouseDownHandler
) {
  return (downEvent: MouseEvent) => {
    const downTrackId = clientYToTrackId(downEvent.clientY);
    const downBeats = clientXToBeats(downEvent.clientX);

    const downCanvasRelativePx = downEvent.clientX - getWrapperRect().left;

    const downContentRelativePx = downCanvasRelativePx - scrollXRef.current;

    const downSeconds = getSecondsFromZero(
      downBeats,
      getDerivedTiming(stateRef.current)
    );

    let isDrag = false;

    const { onMouseMove, onMouseUp } = onMouseDown({
      downEvent,
      downSeconds,
      downBeats,
      downCanvasRelativePx,
      downContentRelativePx,
      downTrackId,
    });

    if (!onMouseMove && !onMouseUp) return;

    const handleMouseMove = (moveEvent: MouseEvent) => {
      const moveTrackId = clientYToTrackId(moveEvent.clientY);
      const moveCanvasRelativePx = moveEvent.clientX - getWrapperRect().left;

      const moveContentRelativePx = moveCanvasRelativePx - scrollXRef.current;

      const moveBeats = clientXToBeats(moveEvent.clientX);
      const moveSeconds = getSecondsFromZero(
        moveBeats,
        getDerivedTiming(stateRef.current)
      );
      if (
        Math.abs(moveCanvasRelativePx - downCanvasRelativePx) > DRAG_THRESHOLD
      ) {
        isDrag = true;
      } else if (moveTrackId !== downTrackId) {
        isDrag = true;
      }

      onMouseMove?.({
        moveEvent,
        moveSeconds,
        moveBeats,
        moveCanvasRelativePx,
        moveContentRelativePx,
        isDrag,
        moveTrackId,
      });
    };

    let lastX = 0;
    let lastY = 0;

    const handleTouchMove = (moveEvent: TouchEvent) => {
      lastX = moveEvent.touches[0].clientX;
      lastY = moveEvent.touches[0].clientY;
      const mouseEvent = new MouseEvent('mousemove', {
        clientX: lastX,
        clientY: lastY,
        button: 0,
        buttons: 1,
        ctrlKey: moveEvent.ctrlKey,
        shiftKey: moveEvent.shiftKey,
        altKey: moveEvent.altKey,
        metaKey: moveEvent.metaKey,
      });
      handleMouseMove(mouseEvent);
    };

    const handleMouseUp = (upEvent: MouseEvent) => {
      const upTrackId = clientYToTrackId(upEvent.clientY);
      const upCanvasRelativePx = upEvent.clientX - getWrapperRect().left;

      const upContentRelativePx = upCanvasRelativePx - scrollXRef.current;

      const upBeats = clientXToBeats(upEvent.clientX);

      const upSeconds = getSecondsFromZero(
        upBeats,
        getDerivedTiming(stateRef.current)
      );

      onMouseUp?.({
        upEvent,
        upSeconds,
        upBeats,
        upCanvasRelativePx,
        upContentRelativePx,
        isDrag,
        upTrackId,
      });

      window.removeEventListener('touchmove', handleTouchMove);
      window.removeEventListener('touchend', handleTouchEnd);
      window.removeEventListener('mousemove', handleMouseMove);
      window.removeEventListener('mouseup', handleMouseUp);
    };

    const handleTouchEnd = (upEvent: TouchEvent) => {
      const mouseEvent = new MouseEvent('mouseup', {
        clientX: upEvent.touches[0]?.clientX ?? lastX,
        clientY: upEvent.touches[0]?.clientY ?? lastY,
        button: 0,
        buttons: 1,
        ctrlKey: upEvent.ctrlKey,
        shiftKey: upEvent.shiftKey,
        altKey: upEvent.altKey,
        metaKey: upEvent.metaKey,
      });
      handleMouseUp(mouseEvent);
    };

    window.addEventListener('touchmove', handleTouchMove);
    window.addEventListener('touchend', handleTouchEnd);
    window.addEventListener('mousemove', handleMouseMove);
    window.addEventListener('mouseup', handleMouseUp);
  };
}
