import { v4 as uuidv4 } from 'uuid';

import moveClip from '../actionHelpers/moveClip';
import moveClipCreationIntent from '../actionHelpers/moveClipCreationIntent';
import clearTimeFromClipList from '../clearTimeFromClipList';
import createTrack from '../createTrack';
import insertClipsToList from '../insertClipsToList';
import {
  getDerivedTiming,
  getSelectionEndBeats,
  getSelectionStartBeats,
  getTracksAndExpandedTakeLanes,
} from '../selectors';
import shouldShowTakeLanes from '../shouldShowTakeLanes';
import {
  StudioClip,
  StudioProjectState,
  StudioTakeLane,
  StudioTrack,
} from '../types';
import { clipIdsByUploadId } from '../uploadedClipCache';
import deleteTimelineSelection from './deleteTimelineSelection';
import deleteTracks from './deleteTracks';
import magneticDeleteTimelineSelection from './magneticDeleteTimelineSelection';

export type TimelineStudioCopiedContent = {
  type: 'timeline';
  tracks: {
    clips: StudioClip[];
  }[];
  startBeats: number;
  endBeats: number;
};

export type TracksStudioCopiedContent = {
  type: 'tracks';
  tracks: StudioTrack[];
};

export type StudioCopiedContent =
  | TimelineStudioCopiedContent
  | TracksStudioCopiedContent;

let copiedContent: StudioCopiedContent | null = null;

export const getSelectedContent = (
  state: StudioProjectState
): StudioCopiedContent | null => {
  if (state.selection.focusedArea === 'timeline') {
    return {
      type: 'timeline',
      tracks: getTracksAndExpandedTakeLanes(state)
        .filter((track) => state.selection.trackIds.includes(track.id))
        .map((track) => ({
          clips: clearTimeFromClipList(
            getDerivedTiming(state),
            getSelectionEndBeats(state),
            Infinity,
            clearTimeFromClipList(
              getDerivedTiming(state),
              -Infinity,
              getSelectionStartBeats(state),
              track.clips
            )
          ),
        })),
      startBeats: getSelectionStartBeats(state),
      endBeats: getSelectionEndBeats(state),
    };
  } else if (state.selection.focusedArea === 'tracks') {
    return {
      type: 'tracks',
      tracks: state.tracks.filter((track) =>
        state.selection.trackIds.includes(track.id)
      ),
    };
  } else {
    return null;
  }
};

export function copy(state: StudioProjectState) {
  copiedContent = getSelectedContent(state);
  return state;
}

export function cut(state: StudioProjectState) {
  copy(state);
  if (state.selection.focusedArea === 'timeline') {
    return deleteTimelineSelection(state);
  } else if (state.selection.focusedArea === 'tracks') {
    return deleteTracks(state.selection.trackIds)(state);
  }
  return state;
}

export function magneticCut(state: StudioProjectState) {
  copy(state);
  if (state.selection.focusedArea === 'timeline') {
    return magneticDeleteTimelineSelection(state);
  } else if (state.selection.focusedArea === 'tracks') {
    return deleteTracks(state.selection.trackIds)(state);
  }
  return state;
}

export function duplicate(state: StudioProjectState) {
  let result = state;
  const copiedContentBefore = copiedContent;
  let newState = copy(state);
  if (copiedContent?.type === 'timeline') {
    newState = {
      ...newState,
      selection: {
        ...newState.selection,
        anchorBeats: getSelectionEndBeats(newState),
        focusBeats:
          getSelectionEndBeats(newState) +
          (getSelectionEndBeats(newState) - getSelectionStartBeats(newState)),
      },
    };

    result = paste(newState);
  } else if (copiedContent?.type === 'tracks') {
    const lastSelectedTrackId = copiedContent.tracks.at(-1)?.id;
    const lastSelectedTrack = state.tracks.find(
      (t) => t.id === lastSelectedTrackId
    );
    const tracksToAdd = copiedContent.tracks.map((t) => ({
      ...t,
      id: uuidv4(),
      name: t.name || '',
      arm: false,
      clips: t.clips.map((c) => ({
        ...c,
        id: uuidv4(),
      })),
      clipCreationIntents: t.clipCreationIntents.map((c) => ({
        ...c,
        id: uuidv4(),
      })),
      takeLanes: t.takeLanes.map((t) => ({
        ...t,
        clips: t.clips.map((c) => ({
          ...c,
          id: uuidv4(),
        })),
        clipCreationIntents: t.clipCreationIntents.map((c) => ({
          ...c,
          id: uuidv4(),
        })),
        id: uuidv4(),
      })),
    }));
    const newTracks = [...state.tracks];
    if (lastSelectedTrack) {
      newTracks.splice(
        newTracks.indexOf(lastSelectedTrack) + 1,
        0,
        ...tracksToAdd
      );
    } else {
      newTracks.push(...tracksToAdd);
    }
    result = {
      ...state,
      tracks: newTracks,
      selection: {
        ...state.selection,
        trackIds: tracksToAdd.map((t) => t.id),
        focusedTrackId: tracksToAdd[0]?.id || null,
      },
    };
  }
  copiedContent = copiedContentBefore;
  return result;
}

export function magneticDuplicate(state: StudioProjectState) {
  let result = state;
  const copiedContentBefore = copiedContent;
  let newState = copy(state);
  if (copiedContent?.type === 'timeline') {
    newState = {
      ...newState,
      selection: {
        ...newState.selection,
        anchorBeats: getSelectionEndBeats(newState),
        focusBeats: getSelectionEndBeats(newState),
      },
    };

    result = magneticPaste(newState);
  }
  copiedContent = copiedContentBefore;
  return result;
}

export function paste(state: StudioProjectState) {
  if (!copiedContent) return state;
  if (copiedContent.type === 'timeline') {
    const tracksAndTakeLanes = getTracksAndExpandedTakeLanes(state);
    const firstSelectedTrackOrTakeLane = tracksAndTakeLanes.find((t) =>
      state.selection.trackIds.includes(t.id)
    );

    if (!firstSelectedTrackOrTakeLane) return state;
    const selectionStartBeats = getSelectionStartBeats(state);

    const contentToPaste = [...copiedContent.tracks];
    const newTracks: StudioTrack[] = [];
    let pasting = false;
    const affectedTracksAndTakeLanes: string[] = [];

    for (let i = 0; i < state.tracks.length; i++) {
      if (state.tracks[i].id === firstSelectedTrackOrTakeLane.id) {
        pasting = true;
      }
      if (pasting && contentToPaste.length > 0) {
        affectedTracksAndTakeLanes.push(state.tracks[i].id);
        newTracks.push({
          ...state.tracks[i],
          clips: insertClipsToList(
            getDerivedTiming(state),
            contentToPaste.shift()!.clips.map(
              (c) =>
                ({
                  ...c,
                  id: uuidv4(),
                  uploadId:
                    c.uploadId && clipIdsByUploadId[c.uploadId]
                      ? null
                      : c.uploadId,
                  clipId:
                    c.uploadId && clipIdsByUploadId[c.uploadId]
                      ? clipIdsByUploadId[c.uploadId]
                      : c.clipId,
                  startBeats:
                    c.startBeats -
                    (copiedContent as TimelineStudioCopiedContent).startBeats +
                    selectionStartBeats,
                  endBeats:
                    c.endBeats -
                    (copiedContent as TimelineStudioCopiedContent).startBeats +
                    selectionStartBeats,
                }) as StudioClip
            ),
            state.tracks[i].clips
          ),
        });
      } else {
        newTracks.push(state.tracks[i]);
      }

      if (shouldShowTakeLanes(state.tracks[i])) {
        const newTakeLanes: StudioTakeLane[] = [];
        for (let j = 0; j < state.tracks[i].takeLanes.length; j++) {
          if (
            state.tracks[i].takeLanes[j].id === firstSelectedTrackOrTakeLane.id
          ) {
            pasting = true;
          }

          if (pasting && contentToPaste.length > 0) {
            affectedTracksAndTakeLanes.push(state.tracks[i].takeLanes[j].id);
            newTakeLanes.push({
              ...state.tracks[i].takeLanes[j],
              clips: insertClipsToList(
                getDerivedTiming(state),
                contentToPaste.shift()!.clips.map(
                  (c) =>
                    ({
                      ...c,
                      id: uuidv4(),
                      uploadId:
                        c.uploadId && clipIdsByUploadId[c.uploadId]
                          ? null
                          : c.uploadId,
                      clipId:
                        c.uploadId && clipIdsByUploadId[c.uploadId]
                          ? clipIdsByUploadId[c.uploadId]
                          : c.clipId,
                      startBeats:
                        c.startBeats -
                        (copiedContent as TimelineStudioCopiedContent)
                          .startBeats +
                        selectionStartBeats,
                      endBeats:
                        c.endBeats -
                        (copiedContent as TimelineStudioCopiedContent)
                          .startBeats +
                        selectionStartBeats,
                    }) as StudioClip
                ),
                state.tracks[i].takeLanes[j].clips
              ),
            });
          } else {
            newTakeLanes.push(state.tracks[i].takeLanes[j]);
          }
        }
        newTracks[newTracks.length - 1] = {
          ...newTracks[newTracks.length - 1],
          takeLanes: newTakeLanes,
        };
      }
    }

    contentToPaste.forEach((track) => {
      newTracks.push({
        ...createTrack(),
        clips: track.clips.map(
          (c) =>
            ({
              ...c,
              id: uuidv4(),
              uploadId:
                c.uploadId && clipIdsByUploadId[c.uploadId] ? null : c.uploadId,
              clipId:
                c.uploadId && clipIdsByUploadId[c.uploadId]
                  ? clipIdsByUploadId[c.uploadId]
                  : c.clipId,
              startBeats:
                c.startBeats -
                (copiedContent as TimelineStudioCopiedContent).startBeats +
                selectionStartBeats,
              endBeats:
                c.endBeats -
                (copiedContent as TimelineStudioCopiedContent).startBeats +
                selectionStartBeats,
            }) as StudioClip
        ),
      });
    });

    if (affectedTracksAndTakeLanes.length > 0) {
      return {
        ...state,
        selection: {
          ...state.selection,
          trackIds: affectedTracksAndTakeLanes,
          focusedTrackId: affectedTracksAndTakeLanes[0] || null,
          anchorBeats: selectionStartBeats,
          focusBeats:
            selectionStartBeats +
            (copiedContent as TimelineStudioCopiedContent).endBeats -
            (copiedContent as TimelineStudioCopiedContent).startBeats,
        },
        tracks: newTracks,
      };
    } else {
      return state;
    }
  }
  return state;
}

export function magneticPaste(state: StudioProjectState) {
  if (!copiedContent) return state;
  if (copiedContent.type === 'timeline') {
    const firstSelectedTrack = state.tracks.find((t) =>
      state.selection.trackIds.includes(t.id)
    );
    if (!firstSelectedTrack) return state;
    const selectionStartBeats = getSelectionStartBeats(state);
    const selectionEndBeats = getSelectionEndBeats(state);

    const deletedContentDuration = selectionEndBeats - selectionStartBeats;

    const copiedContentStartBeats = copiedContent.startBeats;
    const copiedContentEndBeats = copiedContent.endBeats;

    const copiedContentDuration =
      copiedContentEndBeats - copiedContentStartBeats;

    const contentToPaste = [...copiedContent.tracks];
    const newTracks: StudioTrack[] = [];
    let pasting = false;
    const affectedTracks: string[] = [];

    for (let i = 0; i < state.tracks.length; i++) {
      if (state.tracks[i].id === firstSelectedTrack.id) {
        pasting = true;
      }
      if (pasting && contentToPaste.length > 0) {
        affectedTracks.push(state.tracks[i].id);
        let newClips = [...state.tracks[i].clips];
        newClips = clearTimeFromClipList(
          getDerivedTiming(state),
          selectionStartBeats,
          selectionEndBeats,
          newClips
        );
        const timeDelta = copiedContentDuration - deletedContentDuration;
        const newClipCreationIntents = state.tracks[i].clipCreationIntents.map(
          (c) =>
            (c.startBeats ?? -Infinity) >= selectionStartBeats
              ? moveClipCreationIntent(c, timeDelta)
              : c
        );
        newClips = newClips.map((c) =>
          c.startBeats >= selectionStartBeats ? moveClip(c, timeDelta) : c
        );
        newClips = insertClipsToList(
          getDerivedTiming(state),
          contentToPaste.shift()!.clips.map((c) => ({
            ...c,
            id: uuidv4(),
            startBeats:
              c.startBeats - copiedContentStartBeats + selectionStartBeats,
            endBeats:
              c.endBeats - copiedContentStartBeats + selectionStartBeats,
          })),
          newClips
        );
        newTracks.push({
          ...state.tracks[i],
          clips: newClips,
          clipCreationIntents: newClipCreationIntents,
        });
      } else {
        newTracks.push(state.tracks[i]);
      }
    }
    if (affectedTracks.length > 0) {
      return {
        ...state,
        selection: {
          ...state.selection,
          trackIds: affectedTracks,
          focusedTrackId: affectedTracks[0] || null,
          anchorBeats: selectionStartBeats,
          focusBeats: selectionStartBeats + copiedContentDuration,
        },
        tracks: newTracks,
      };
    } else {
      return state;
    }
  }
  return state;
}

export const getCanPaste = () => {
  if (!copiedContent) return false;
  if (copiedContent.type === 'timeline') {
    return copiedContent.tracks.every((t) => t.clips.length > 0);
  }
  return false;
};
