import { binarySearchLowerBound } from './binarySearch';
import { StudioClip } from './projectState/fixClip';
import { getEffectiveMarkers } from './warpUtils';

const anchorSourceDestinationCache = new Map<
  Record<number, number>,
  number[][]
>();

const getAnchorSourcesAndDestinations = (
  warpMarkers: Record<number, number>
) => {
  const hadCache = anchorSourceDestinationCache.has(warpMarkers);
  const anchorSourcesAndDestinations = hadCache
    ? anchorSourceDestinationCache.get(warpMarkers)!
    : Object.entries(warpMarkers)
        .map(([source, destination]) => [Number(source), Number(destination)])
        .sort((a, b) => (a[1] > b[1] ? 1 : -1));

  if (!hadCache) {
    if (anchorSourceDestinationCache.size > 1000) {
      anchorSourceDestinationCache.delete(
        anchorSourceDestinationCache.keys().next().value!
      );
    }
    anchorSourceDestinationCache.set(
      warpMarkers,
      anchorSourcesAndDestinations!
    );
  }

  return anchorSourcesAndDestinations;
};

export const getClipContentBeats = (
  clip: StudioClip,
  contentSeconds: number
) => {
  const warpMarkers = getEffectiveMarkers(clip.warp);
  if (Object.keys(warpMarkers).length === 0) {
    return contentSeconds;
  }

  const anchorSourcesAndDestinations =
    getAnchorSourcesAndDestinations(warpMarkers);

  if (anchorSourcesAndDestinations.length === 0) {
    return contentSeconds;
  }

  const getAnchorDelta = (index: number) =>
    warpMarkers[anchorSourcesAndDestinations[index][0]] -
    anchorSourcesAndDestinations[index][0];

  // unwarped space on either side of the first and last marker.
  if (contentSeconds <= anchorSourcesAndDestinations[0][0]) {
    return contentSeconds + getAnchorDelta(0);
  } else if (
    contentSeconds >=
    anchorSourcesAndDestinations[anchorSourcesAndDestinations.length - 1][0]
  ) {
    return (
      contentSeconds + getAnchorDelta(anchorSourcesAndDestinations.length - 1)
    );
  }

  // find the anchors on either side of contentSeconds.
  const anchorBeforeIndex = binarySearchLowerBound(
    anchorSourcesAndDestinations,
    anchorSourcesAndDestinations.length,
    contentSeconds,
    (x) => x[0]
  );
  const anchorAfterIndex = Math.min(
    anchorBeforeIndex + 1,
    anchorSourcesAndDestinations.length - 1
  );

  const anchorBefore = anchorSourcesAndDestinations[anchorBeforeIndex][0];
  const anchorAfter = anchorSourcesAndDestinations[anchorAfterIndex][0];

  if (anchorAfter === anchorBefore) {
    return contentSeconds;
  }

  const proportion =
    (contentSeconds - anchorBefore) / (anchorAfter - anchorBefore);

  const warpBefore =
    warpMarkers[anchorSourcesAndDestinations[anchorBeforeIndex][0]];
  const warpAfter =
    warpMarkers[anchorSourcesAndDestinations[anchorAfterIndex][0]];

  return warpBefore + (warpAfter - warpBefore) * proportion;
};
