import { AlignedLyric } from '../edit2025/types';
import { getAlignedLyricsByTrackId } from './selectors';
import splitBeatAlignedLyrics from './splitBeatAlignedLyrics';
import { BeatAlignedLyric, StudioProjectState } from './types';

// Helper function to safely get beat value from timing
const getBeatValue = (
  timing: BeatAlignedLyric['timing'],
  fallback: number = Infinity
): number => {
  if (!timing) return fallback;
  if (timing.type === 'point') return timing.beats;
  if (timing.type === 'range') return timing.startBeats;
  return fallback;
};

const interpolateLyricsInto = (
  existingLyrics: BeatAlignedLyric[],
  newLyrics: BeatAlignedLyric[]
) => {
  const result: BeatAlignedLyric[] = [];
  let existingIndex = 0;
  let newIndex = 0;

  while (existingIndex < existingLyrics.length || newIndex < newLyrics.length) {
    const existingLyric = existingLyrics[existingIndex];
    const newLyric = newLyrics[newIndex];

    // If we've exhausted existing lyrics, add remaining new lyrics
    if (existingIndex >= existingLyrics.length) {
      result.push(newLyric);
      newIndex++;
      continue;
    }

    // If we've exhausted new lyrics, add remaining existing lyrics
    if (newIndex >= newLyrics.length) {
      result.push(existingLyric);
      existingIndex++;
      continue;
    }

    // Handle untimed lyrics in new lyrics array
    if (!newLyric.timing) {
      // Check if the next timed lyric in new lyrics will be included
      let shouldIncludeUntimed = false;

      // Look ahead for the next timed lyric in new lyrics
      for (let i = newIndex + 1; i < newLyrics.length; i++) {
        if (newLyrics[i].timing) {
          // Check if this timed lyric should come before the current existing lyric
          const newLyricBeats = getBeatValue(newLyrics[i].timing);
          const existingLyricBeats = getBeatValue(existingLyric.timing);

          if (newLyricBeats < existingLyricBeats) {
            shouldIncludeUntimed = true;
          }
          break;
        }
      }

      // Also check if the previous timed lyric in new lyrics was included
      if (!shouldIncludeUntimed) {
        for (let i = newIndex - 1; i >= 0; i--) {
          if (newLyrics[i].timing) {
            shouldIncludeUntimed = true;
            break;
          }
        }
      }

      if (shouldIncludeUntimed) {
        result.push(newLyric);
        newIndex++;
      } else {
        // Skip this untimed lyric
        newIndex++;
      }
      continue;
    }

    // Handle untimed lyrics in existing lyrics array
    if (!existingLyric.timing) {
      // Check if the next timed lyric in existing lyrics will be included
      let shouldIncludeUntimed = false;

      // Look ahead for the next timed lyric in existing lyrics
      for (let i = existingIndex + 1; i < existingLyrics.length; i++) {
        if (existingLyrics[i].timing) {
          // Check if this timed lyric should come before the current new lyric
          const existingLyricBeats = getBeatValue(existingLyrics[i].timing);
          const newLyricBeats = getBeatValue(newLyric.timing);

          if (existingLyricBeats < newLyricBeats) {
            shouldIncludeUntimed = true;
          }
          break;
        }
      }

      // Also check if the previous timed lyric in existing lyrics was included
      if (!shouldIncludeUntimed) {
        for (let i = existingIndex - 1; i >= 0; i--) {
          if (existingLyrics[i].timing) {
            shouldIncludeUntimed = true;
            break;
          }
        }
      }

      if (shouldIncludeUntimed) {
        result.push(existingLyric);
        existingIndex++;
      } else {
        // Skip this untimed lyric
        existingIndex++;
      }
      continue;
    }

    // Both lyrics are timed, compare their timing
    const existingBeats = getBeatValue(existingLyric.timing);
    const newBeats = getBeatValue(newLyric.timing);

    if (existingBeats <= newBeats) {
      result.push(existingLyric);
      existingIndex++;
    } else {
      result.push(newLyric);
      newIndex++;
    }
  }

  return result;
};

export default function getStateAlignedLyrics(
  state: StudioProjectState,
  alignedLyricsByClipId: Record<string, AlignedLyric[]>,
  options: {
    trackIds?: string[];
    startBeats?: number;
    endBeats?: number;
  } = {}
) {
  const { trackIds, startBeats, endBeats } = options;

  const alignedLyricsByTrackId = getAlignedLyricsByTrackId({
    state,
    alignedLyricsByClipId,
  });

  let trimmedLyricsByTrackId = alignedLyricsByTrackId;
  if (trackIds) {
    trimmedLyricsByTrackId = Object.fromEntries(
      trackIds.map((trackId) => [
        trackId,
        alignedLyricsByTrackId[trackId] || [],
      ])
    );
  }

  const effectiveStartBeats = startBeats ?? -Infinity;
  const effectiveEndBeats = endBeats ?? Infinity;

  if (Number.isFinite(startBeats) || Number.isFinite(endBeats)) {
    trimmedLyricsByTrackId = Object.fromEntries(
      Object.entries(trimmedLyricsByTrackId).map(([trackId, lyrics]) => [
        trackId,
        splitBeatAlignedLyrics(
          lyrics,
          effectiveStartBeats,
          effectiveEndBeats
        )[1],
      ])
    );
  }

  const lyricsArraysInOrderOfLength = Object.values(
    trimmedLyricsByTrackId
  ).sort((lyricsA, lyricsB) => lyricsB.length - lyricsA.length);

  let result: BeatAlignedLyric[] = [];

  for (const lyricsArray of lyricsArraysInOrderOfLength) {
    // don't add if there are no timed lyrics
    if (lyricsArray.every((l) => !l.timing || l.timing.type === 'point'))
      continue;
    result = interpolateLyricsInto(result, lyricsArray);
  }

  return result;
}
