// Parse LRC-format synced lyrics into timestamped lines.
//
// The worker stores lrclib's `syncedLyrics` (a raw LRC string) JSON-encoded in
// the track_lyrics.synced_lyrics JSONB column, so it comes back over the API as
// a JSON string. LRC looks like:
//   [ar:Artist]            <- metadata tag, ignored
//   [00:12.34]First line
//   [00:15.80]Second line
//   [01:02.5]              <- empty line (instrumental gap) kept as ''
// A single text line can carry several timestamps ("[00:01][00:05]chorus").

export interface LyricLine {
  /** Start time in seconds. */
  time: number;
  text: string;
}

const TIMESTAMP_RE = /\[(\d{1,2}):(\d{2}(?:[.:]\d{1,3})?)\]/g;

/**
 * Parse an LRC string into time-sorted lines. Returns [] when the input has no
 * timestamped lines (e.g. plain-text lyrics or null), so callers can fall back.
 */
export function parseLrc(input: unknown): LyricLine[] {
  if (typeof input !== 'string' || input.trim() === '') return [];

  const lines: LyricLine[] = [];
  for (const raw of input.split(/\r?\n/)) {
    TIMESTAMP_RE.lastIndex = 0;
    const stamps: number[] = [];
    let match: RegExpExecArray | null;
    let lastEnd = 0;
    while ((match = TIMESTAMP_RE.exec(raw)) !== null) {
      const min = parseInt(match[1], 10);
      const sec = parseFloat(match[2].replace(':', '.'));
      stamps.push(min * 60 + sec);
      lastEnd = match.index + match[0].length;
    }
    if (stamps.length === 0) continue; // metadata tag or untimed line
    const text = raw.slice(lastEnd).trim();
    for (const time of stamps) lines.push({ time, text });
  }

  lines.sort((a, b) => a.time - b.time);
  return lines;
}

/**
 * Index of the active line for a given playback position (the last line whose
 * timestamp is <= position). Returns -1 before the first line. Lines must be
 * time-sorted (as returned by parseLrc).
 */
export function activeLineIndex(lines: LyricLine[], position: number): number {
  let lo = 0;
  let hi = lines.length - 1;
  let result = -1;
  while (lo <= hi) {
    const mid = (lo + hi) >> 1;
    if (lines[mid].time <= position) {
      result = mid;
      lo = mid + 1;
    } else {
      hi = mid - 1;
    }
  }
  return result;
}
