import { pitchToMultiplier } from '@suno/studiokit/audioEngineeringUtils';
import {
  getClipBeatsToClipContentSeconds,
  getClipContentSecondsToClipBeats,
} from '@suno/studiokit/timeMapping';
import {
  addSecondsToBeats,
  getSecondsBetween,
} from '@suno/studiokit/timeMapping';
import { getWarpEnabledAndPopulated } from '@suno/studiokit/warpUtils';

import {
  TRANSPOSE_MAXIMUM_SEMITONES,
  TRANSPOSE_MINIMUM_SEMITONES,
} from '@/components/focusedObjectPanel/StudioClipSettingsSubsection';

import {
  getDerivedTiming,
  getFocusedStudioClip,
  getSelectedClips,
  getSelectionEndBeats,
  getSelectionStartBeats,
  getStudioClipsById,
} from '../selectors';
import { StudioClip, StudioProjectState } from '../types';
import { combineActions } from './combineActions';
import updateClip from './updateClip';

export const updateSingleClipTransposition = (
  studioClipId: string,
  transposition: number
) => {
  return (state: StudioProjectState): StudioProjectState => {
    return updateClip(studioClipId, (studioClip) => {
      if (getWarpEnabledAndPopulated(studioClip.warp)) {
        return {
          ...studioClip,
          transposition,
        };
      } else {
        const timing = getDerivedTiming(state);

        const oldTranspositionMultiplier = pitchToMultiplier(
          studioClip.transposition
        );
        const newTranspositionMultiplier = pitchToMultiplier(transposition);

        const contentDurationSeconds =
          (getSecondsBetween(
            studioClip.startBeats,
            studioClip.endBeats,
            timing
          ) *
            oldTranspositionMultiplier) /
          newTranspositionMultiplier;

        const newEndBeats = addSecondsToBeats(
          contentDurationSeconds,
          studioClip.startBeats,
          timing
        );

        const beatsToSeconds = getClipBeatsToClipContentSeconds(
          studioClip,
          contentDurationSeconds
        );
        const readStartSeconds = beatsToSeconds(studioClip.readStartBeats)?.[1];

        const newStudioClip = {
          ...studioClip,
          transposition,
          endBeats: newEndBeats,
        };

        const secondsToBeats = getClipContentSecondsToClipBeats(
          newStudioClip,
          contentDurationSeconds
        );

        const newReadStartBeats =
          readStartSeconds != null
            ? (secondsToBeats(readStartSeconds)?.[1] ??
              studioClip.readStartBeats)
            : studioClip.readStartBeats;
        return {
          ...newStudioClip,
          readStartBeats: newReadStartBeats,
        };
      }
    })(state);
  };
};

export default function updateClipTransposition(
  studioClipIds: string[],
  transposition: number,
  delta?: boolean
) {
  return (state: StudioProjectState): StudioProjectState => {
    const clipsById = getStudioClipsById(state);
    const targetClips = studioClipIds
      .map((id) => clipsById[id])
      .filter(Boolean) as StudioClip[];
    const focusedClip = getFocusedStudioClip(state);
    return combineActions(
      ...targetClips.map((c) =>
        updateSingleClipTransposition(
          c.id,
          Math.min(
            TRANSPOSE_MAXIMUM_SEMITONES,
            Math.max(
              TRANSPOSE_MINIMUM_SEMITONES,
              delta ? c.transposition + transposition : transposition
            )
          )
        )
      ),
      (state) => ({
        ...state,
        selection: focusedClip
          ? {
              ...state.selection,
              anchorBeats: getStudioClipsById(state)[focusedClip.id].startBeats,
              focusBeats: getStudioClipsById(state)[focusedClip.id].endBeats,
            }
          : {
              ...state.selection,
              anchorBeats: Math.min(
                ...getSelectedClips(state).map((c) => c.startBeats),
                getSelectionStartBeats(state)
              ),
              focusBeats: Math.max(
                ...getSelectedClips(state).map((c) => c.endBeats),
                getSelectionEndBeats(state)
              ),
            },
      })
    )(state);
  };
}
