import { useCallback, useRef } from 'react';

import { DraggedObject } from '@/app/(root)/dragAndDrop/DragAndDropContext';
import Droppable, { DropOverlay } from '@/app/(root)/dragAndDrop/Droppable';
import { useContextSelector } from '@/hooks/useContextSelector';
import { snapWithEvent } from '@/utils/snap';

import StudioContext from './StudioContext';
import { applyInsertionSpec, getClosestInsertionSpec } from './insertion';
import useDraggedClipsForEditV3 from './useDraggedClipsForEditV3';

export default function EditV3TimelineDroppable({
  children,
}: {
  children: React.ReactNode;
}) {
  const frameCountRef = useContextSelector(
    StudioContext,
    (context) => context.timelineController.frameCountRef
  );

  const {
    arrangementPackages,
    createPointInsertionSpecs,
    getFirstClipInsertionSpecs,
  } = useDraggedClipsForEditV3();

  const clientYToTrackId = useContextSelector(
    StudioContext,
    (context) => context.clientYToTrackId
  );

  const clientXToBeats = useContextSelector(
    StudioContext,
    (context) => context.clientXToBeats
  );

  const dragStateRef = useContextSelector(
    StudioContext,
    (context) => context.dragStateRef
  );

  const currentDragCoordinatesRef = useRef<{
    clientX: number;
    clientY: number;
  } | null>(null);

  const gridSizeRef = useContextSelector(
    StudioContext,
    (context) => context.gridSizeRef
  );

  const handleDragMove = useCallback(
    (
      _draggedObject: DraggedObject<'clip' | 'multi-clip'>,
      event: React.MouseEvent<HTMLDivElement>
    ) => {
      if (!arrangementPackages.length) return;
      if (currentDragCoordinatesRef.current) {
        const { clientX, clientY } = currentDragCoordinatesRef.current;
        if (clientX === event.clientX && clientY === event.clientY) return;
      }
      currentDragCoordinatesRef.current = {
        clientX: event.clientX,
        clientY: event.clientY,
      };
      const targetTrackId = clientYToTrackId(event.clientY);
      const rawTargetBeats = clientXToBeats(event.clientX);
      const targetBeats = snapWithEvent(
        event.nativeEvent,
        rawTargetBeats,
        gridSizeRef.current
      );
      const insertionSpecs = createPointInsertionSpecs(
        arrangementPackages,
        targetTrackId,
        targetBeats
      );
      const targetInsertionSpec =
        getClosestInsertionSpec(
          getFirstClipInsertionSpecs(),
          targetTrackId,
          targetBeats
        ) ?? insertionSpecs[0];

      dragStateRef.current = {
        studioClip: arrangementPackages[0].studioClip,
        trackId: targetTrackId,
        active: true,
        insertionSpecs: [],
        targetInsertionSpec,
      };
      frameCountRef.current++;
    },
    [
      clientYToTrackId,
      clientXToBeats,
      createPointInsertionSpecs,
      arrangementPackages,
      dragStateRef,
      frameCountRef,
    ]
  );

  const previewOrSetState = useContextSelector(
    StudioContext,
    (context) => context.previewOrSetState
  );

  const handleDrop = useCallback(() => {
    if (dragStateRef.current && dragStateRef.current.targetInsertionSpec) {
      previewOrSetState(
        dragStateRef.current.targetInsertionSpec.clip.id,
        applyInsertionSpec(dragStateRef.current.targetInsertionSpec)
      );
    }
    dragStateRef.current = null;
    frameCountRef.current++;
  }, [dragStateRef, frameCountRef, previewOrSetState]);

  const handleDragLeave = useCallback(() => {
    dragStateRef.current = null;
    frameCountRef.current++;
  }, [dragStateRef, frameCountRef]);
  return (
    <Droppable
      accept={['clip']}
      onDrop={handleDrop}
      onDragMove={handleDragMove}
      onDragLeave={handleDragLeave}
      overlay={
        <DropOverlay>
          {arrangementPackages.length
            ? 'Drop here'
            : 'Clips analyzing, please wait'}
        </DropOverlay>
      }
    >
      {children}
    </Droppable>
  );
}
