import snap from '@/utils/snap';

export default function getClipSnap(
  startBeats: number,
  endBeats: number,
  mouseDeltaBeats: number,
  absoluteAnchors: number[],
  gridSizeBeats: number,
  maxShiftBeats: number,
  firstVisibleBeat: number,
  lastVisibleBeat: number
) {
  const impliedStart = startBeats + mouseDeltaBeats;
  const impliedEnd = endBeats + mouseDeltaBeats;
  let possibleAnchors: number[] = [];
  if (mouseDeltaBeats === 0) {
    possibleAnchors = absoluteAnchors;
  } else if (mouseDeltaBeats > 0) {
    possibleAnchors = absoluteAnchors.filter((anchor) => anchor > startBeats);
  } else {
    possibleAnchors = absoluteAnchors.filter((anchor) => anchor < endBeats);
  }
  const lastAnchorBeforeStart = possibleAnchors.reduce((acc, anchor) => {
    if (anchor < impliedStart && anchor > acc) {
      return anchor;
    }
    return acc;
  }, -Infinity);
  const firstAnchorAfterStart = possibleAnchors.reduce((acc, anchor) => {
    if (anchor > impliedStart && anchor < acc) {
      return anchor;
    }
    return acc;
  }, Infinity);
  const lastAnchorBeforeEnd = possibleAnchors.reduce((acc, anchor) => {
    if (anchor < impliedEnd && anchor > acc) {
      return anchor;
    }
    return acc;
  }, -Infinity);
  const firstAnchorAfterEnd = possibleAnchors.reduce((acc, anchor) => {
    if (anchor > impliedEnd && anchor < acc) {
      return anchor;
    }
    return acc;
  }, Infinity);

  const anchorDeltas: number[] = [];
  if (Number.isFinite(lastAnchorBeforeStart)) {
    anchorDeltas.push(lastAnchorBeforeStart - startBeats);
  }
  if (Number.isFinite(firstAnchorAfterStart)) {
    anchorDeltas.push(firstAnchorAfterStart - startBeats);
  }
  if (Number.isFinite(lastAnchorBeforeEnd)) {
    anchorDeltas.push(lastAnchorBeforeEnd - endBeats);
  }
  if (Number.isFinite(firstAnchorAfterEnd)) {
    anchorDeltas.push(firstAnchorAfterEnd - endBeats);
  }

  let relativeGridDelta = snap(mouseDeltaBeats, gridSizeBeats);

  if (mouseDeltaBeats === 0) {
    relativeGridDelta = 0;
  } else if (relativeGridDelta < 0 && mouseDeltaBeats > 0) {
    relativeGridDelta += gridSizeBeats;
  } else if (relativeGridDelta > 0 && mouseDeltaBeats < 0) {
    relativeGridDelta -= gridSizeBeats;
  }

  let absoluteStartGridDelta = snap(impliedStart, gridSizeBeats) - startBeats;
  if (absoluteStartGridDelta < 0 && mouseDeltaBeats > 0) {
    absoluteStartGridDelta += gridSizeBeats;
  } else if (absoluteStartGridDelta > 0 && mouseDeltaBeats < 0) {
    absoluteStartGridDelta -= gridSizeBeats;
  }

  let absoluteEndGridDelta = snap(impliedEnd, gridSizeBeats) - endBeats;
  if (absoluteEndGridDelta < 0 && mouseDeltaBeats > 0) {
    absoluteEndGridDelta += gridSizeBeats;
  } else if (absoluteEndGridDelta > 0 && mouseDeltaBeats < 0) {
    absoluteEndGridDelta -= gridSizeBeats;
  }

  const possibleDeltas = [0, relativeGridDelta, ...anchorDeltas];

  // we snap to the end if only the end is visible, and the start is off the screen.
  const gridSnapEnd =
    impliedEnd > firstVisibleBeat &&
    impliedEnd < lastVisibleBeat &&
    impliedStart < firstVisibleBeat;

  if (gridSnapEnd) {
    possibleDeltas.push(absoluteEndGridDelta);
  } else {
    possibleDeltas.push(absoluteStartGridDelta);
  }

  const closestDelta = possibleDeltas.reduce((acc, delta) => {
    return Math.abs(delta - mouseDeltaBeats) < Math.abs(acc - mouseDeltaBeats)
      ? delta
      : acc;
  }, Infinity);

  if (Math.abs(closestDelta - mouseDeltaBeats) > maxShiftBeats) {
    return mouseDeltaBeats;
  }

  return closestDelta;
}
