import { useQueries } from '@tanstack/react-query';
import { useCallback, useMemo } from 'react';

import { useStores } from '@/app/(root)/AppProviders';
import DragAndDropContext, {
  DraggedClip,
  DraggedMultiClip,
} from '@/app/(root)/dragAndDrop/DragAndDropContext';
import { useContextSelector } from '@/hooks/useContextSelector';

import StudioContext from './StudioContext';
import {
  fetchClipArrangementPackage,
  getClipArrangementPackageQueryKey,
} from './fetchClipArrangementPackage';
import { ClipInsertionSpec } from './insertion';
import { getTrackAndExpandedTakeLaneIds } from './selectors';
import { ClipArrangementPackage } from './types';
import useGetInsertionSpecs from './useGetInsertionSpecs';

export default function useDraggedClipsForEditV3() {
  const { clips: clipsStore } = useStores();

  const draggedObject = useContextSelector(
    DragAndDropContext,
    (ctx) => ctx.draggedObject
  );

  const clipIds = useMemo(() => {
    if (!draggedObject) return [];
    return draggedObject?.type === 'multi-clip'
      ? (draggedObject as DraggedMultiClip).payload.clipIds
      : [(draggedObject as DraggedClip).payload.clipId];
  }, [draggedObject]);

  const arrangementPackageQueries = useQueries({
    queries: clipIds.map((clipId) => ({
      queryKey: getClipArrangementPackageQueryKey(clipId),
      queryFn: () => fetchClipArrangementPackage(clipsStore, clipId),
    })),
  });

  const arrangementPackages = useMemo(() => {
    return arrangementPackageQueries
      .map((query) => query.data)
      .filter(Boolean) as ClipArrangementPackage[];
  }, [arrangementPackageQueries]);

  const getFirstClipInsertionSpecs = useGetInsertionSpecs(
    clipIds.length === 1 ? arrangementPackages[0] : null
  );

  const oneTrackMode = useContextSelector(
    StudioContext,
    (ctx) => ctx.oneTrackMode
  );

  const trackIds = useContextSelector(StudioContext, (ctx) =>
    getTrackAndExpandedTakeLaneIds(ctx.state)
  );

  const magnetMode = useContextSelector(StudioContext, (ctx) => ctx.magnetMode);

  const firstTrackId = useContextSelector(
    StudioContext,
    (ctx) => ctx.state.tracks[0]?.id ?? null
  );

  const createPointInsertionSpecs = useCallback(
    (
      arrangementPackages: ClipArrangementPackage[],
      trackId: string | null,
      beats: number
    ): ClipInsertionSpec[] => {
      if (oneTrackMode) {
        trackId = firstTrackId ?? null;
        arrangementPackages = [arrangementPackages[0]];
      }

      const targetTrackIds = trackId
        ? trackIds.slice(
            trackIds.indexOf(trackId),
            trackIds.indexOf(trackId) + arrangementPackages.length
          )
        : arrangementPackages.map(() => null);

      return arrangementPackages.map((arrangementPackage, index) => {
        const { clip, studioClip } = arrangementPackage;

        let startBeats = beats + studioClip.readStartBeats;
        let endBeats =
          startBeats + (studioClip.endBeats - studioClip.startBeats);

        if (!(startBeats <= beats && endBeats >= beats)) {
          // this can happen if readStartBeats is a positive number, or if the clip is shorter than -readStartBeats
          const offset = startBeats - beats;
          const snappedOffset =
            offset > 0 ? Math.ceil(offset) : Math.floor(offset);
          endBeats -= snappedOffset;
          startBeats -= snappedOffset;
        }

        return {
          clip,
          studioClip: {
            ...studioClip,
            startBeats,
            endBeats,
          },
          replacementStartBeats: beats,
          replacementEndBeats: magnetMode
            ? beats
            : beats + (studioClip.endBeats - studioClip.startBeats),
          trackId: targetTrackIds[index],
        };
      });
    },
    [oneTrackMode, firstTrackId, magnetMode, trackIds]
  );

  return {
    type: draggedObject?.type,
    arrangementPackages: arrangementPackageQueries
      .map((query) => query.data)
      .filter(Boolean) as ClipArrangementPackage[],
    getFirstClipInsertionSpecs,
    createPointInsertionSpecs,
  };
}
