import { getWarpBeatsFromSeconds } from '@suno/studiokit/warpUtils';

import { Clip } from '@/state/clipStore';

import { AlignmentMatch } from './findAlignmentMatches';
import { StudioClip } from './types';

export default function getGridOffsetBeats(studioClip: StudioClip) {
  let result = (studioClip.startBeats - studioClip.readStartBeats) % 1;
  if (result > 0.5) {
    result -= 1;
  } else if (result < -0.5) {
    result += 1;
  }
  return result;
}

/*
1  .  2   .  3
         [2   .  3]

readStartBeats: 2
startBeats: 2.5
gridOffsetBeats: 0.5


1  .  |  .  2  .  |  .  3
        [2  .  |  .  3]

readStartBeats: 2
startBeats: 1.75
gridOffsetBeats: -0.25


1  .  |  .  2  .  |  .  3
        [.  2  .  |  .  3]

readStartBeats: 1.75
startBeats: 1.75
gridOffsetBeats: 0

1  .  |  .  2  .  |  .  3
           [.  2  .  |  .  3]

readStartBeats: 1.75
startBeats: 2
gridOffsetBeats: 0.25

*/

export const alignStartGridOffset = (
  historyClipGridOffset: number,
  studioClip: StudioClip,
  clip: Clip
) => {
  const minReadBeats = getWarpBeatsFromSeconds(studioClip.warp, 0);
  const maxReadBeats = getWarpBeatsFromSeconds(
    studioClip.warp,
    clip.metadata.duration ?? Infinity
  );

  const studioClipGridOffsetBeats = getGridOffsetBeats(studioClip);
  const delta = historyClipGridOffset - studioClipGridOffsetBeats;

  let newReadStartBeats = studioClip.readStartBeats - delta;

  if (
    newReadStartBeats < minReadBeats &&
    newReadStartBeats + 1 <= maxReadBeats
  ) {
    newReadStartBeats++;
  } else if (
    newReadStartBeats > maxReadBeats &&
    newReadStartBeats - 1 >= minReadBeats
  ) {
    newReadStartBeats--;
  } else if (
    newReadStartBeats < minReadBeats ||
    newReadStartBeats > maxReadBeats
  ) {
    // we can't align this studioClip without going past the content bounds, so keep readStartBeats unchanged.
    newReadStartBeats = studioClip.readStartBeats;
  }

  studioClip = {
    ...studioClip,
    readStartBeats: newReadStartBeats,
  };

  return studioClip;
};

// Ensure that the timing offset of the future clip matches the timing offset of the history clip by adjusting the duration of the arranged studioClip.
// For example:
/*
    history clip offset: 0.25,
    future clip offset: 0.5

    - if we wanted to add 4 beats of space between them, we'd instead add 3.75 beats of space to shift the future clip back to the same 0.25 offset as the history.

*/
export const alignEndGridOffset = (
  match: AlignmentMatch,
  studioClip: StudioClip,
  clip: Clip
) => {
  const { futureClipGridOffset, historyClipGridOffset } = match;
  if (futureClipGridOffset === null) {
    return studioClip;
  }

  const minReadBeats = getWarpBeatsFromSeconds(studioClip.warp, 0);
  const maxReadBeats = getWarpBeatsFromSeconds(
    studioClip.warp,
    clip.metadata.duration ?? Infinity
  );

  const clipDurationBeats = studioClip.endBeats - studioClip.startBeats;
  const replacedDurationBeats =
    match.replacementEndBeats - match.replacementStartBeats;
  const durationChangeBeats = replacedDurationBeats - clipDurationBeats;

  let replacedOffsetBeats = durationChangeBeats % 1;
  if (replacedOffsetBeats > 0.5) {
    replacedOffsetBeats -= 1;
  } else if (replacedOffsetBeats < -0.5) {
    replacedOffsetBeats += 1;
  }

  let gridOffsetChange =
    (futureClipGridOffset - (historyClipGridOffset ?? 0)) % 1;
  if (gridOffsetChange > 0.5) {
    gridOffsetChange -= 1;
  } else if (gridOffsetChange < -0.5) {
    gridOffsetChange += 1;
  }

  let newEndBeats =
    studioClip.startBeats +
    clipDurationBeats +
    replacedOffsetBeats -
    gridOffsetChange;

  const newReadEndBeats =
    studioClip.readStartBeats + (newEndBeats - studioClip.startBeats);

  if (newReadEndBeats < minReadBeats && newReadEndBeats + 1 <= maxReadBeats) {
    newEndBeats++;
  } else if (
    newReadEndBeats > maxReadBeats &&
    newReadEndBeats - 1 >= minReadBeats
  ) {
    newEndBeats--;
  } else if (newReadEndBeats < minReadBeats || newReadEndBeats > maxReadBeats) {
    // we can't align this studioClip without going past the content bounds, so keep readStartBeats unchanged.
    newEndBeats = studioClip.endBeats;
  }

  studioClip = {
    ...studioClip,
    endBeats: newEndBeats,
  };

  return studioClip;
};
