// Parsing bridge — ported near-verbatim from desktop
// (astra/src/main/services/lyricsParsing.ts). Converts the `@boof2015/xlrc`
// parser output (XLRCLine) into the app-internal LyricsLine contract, keeping
// rich fields (words/furigana/translations/voice) for XLRC and dropping them
// for plain LRC. Pure functions — no RN/Node dependencies.

import { parseXLRC, type XLRCFile, type XLRCLine } from '@boof2015/xlrc';
import type {
  LyricsFormat,
  LyricsFurigana,
  LyricsLine,
  LyricsPayload,
  LyricsTranslation,
  LyricsWord,
} from './types';

export function normalizeLyricsText(value: unknown): string | null {
  if (typeof value !== 'string') return null;
  const normalized = value.replace(/\r\n/g, '\n').trim();
  return normalized.length > 0 ? normalized : null;
}

function normalizeTimestampMs(value: unknown): number | null {
  return typeof value === 'number' && Number.isFinite(value)
    ? Math.max(0, Math.floor(value))
    : null;
}

function sanitizeFurigana(raw: unknown, text: string): LyricsFurigana[] {
  if (!Array.isArray(raw)) return [];

  const furigana: LyricsFurigana[] = [];
  for (const entry of raw) {
    if (!entry || typeof entry !== 'object' || Array.isArray(entry)) continue;
    const record = entry as { start?: unknown; end?: unknown; base?: unknown; reading?: unknown };
    if (typeof record.base !== 'string' || typeof record.reading !== 'string') continue;
    if (typeof record.start !== 'number' || typeof record.end !== 'number') continue;
    if (!Number.isInteger(record.start) || !Number.isInteger(record.end)) continue;
    const start = record.start;
    const end = record.end;
    if (start < 0 || end <= start || end > text.length) continue;
    const base = record.base.trim();
    const reading = record.reading.trim();
    if (!base || !reading) continue;

    furigana.push({ start, end, base, reading });
  }

  return furigana;
}

function sanitizeWords(raw: unknown): LyricsWord[] {
  if (!Array.isArray(raw)) return [];

  const words: LyricsWord[] = [];
  for (const entry of raw) {
    if (!entry || typeof entry !== 'object' || Array.isArray(entry)) continue;
    const record = entry as { timestampMs?: unknown; timestamp?: unknown; text?: unknown; furigana?: unknown };
    if (typeof record.text !== 'string') continue;
    const timestampMs = normalizeTimestampMs(record.timestampMs ?? record.timestamp);
    if (timestampMs === null) continue;
    const text = record.text;
    if (!text.trim()) continue;
    const furigana = sanitizeFurigana(record.furigana, text);

    words.push({
      timestampMs,
      text,
      ...(furigana.length > 0 ? { furigana } : {}),
    });
  }

  return words;
}

function sanitizeTranslations(raw: unknown): LyricsTranslation[] {
  if (!Array.isArray(raw)) return [];

  const translations: LyricsTranslation[] = [];
  for (const entry of raw) {
    if (!entry || typeof entry !== 'object' || Array.isArray(entry)) continue;
    const record = entry as { lang?: unknown; text?: unknown };
    if (typeof record.lang !== 'string' || typeof record.text !== 'string') continue;
    const lang = record.lang.trim();
    const text = record.text.trim();
    if (!lang || !text) continue;
    translations.push({ lang, text });
  }

  return translations;
}

export function sanitizeLyricsLines(rawValue: unknown): LyricsLine[] {
  if (!Array.isArray(rawValue)) return [];

  const lines: LyricsLine[] = [];
  for (const entry of rawValue) {
    if (!entry || typeof entry !== 'object' || Array.isArray(entry)) continue;
    const record = entry as {
      timestampMs?: unknown;
      text?: unknown;
      kind?: unknown;
      words?: unknown;
      furigana?: unknown;
      translations?: unknown;
      voice?: unknown;
    };
    if (typeof record.text !== 'string') continue;

    const timestampMs = normalizeTimestampMs(record.timestampMs);
    if (timestampMs === null) continue;

    if (record.kind === 'silence') {
      lines.push({ timestampMs, text: '', kind: 'silence' });
      continue;
    }

    const text = record.text.trim();
    if (!text) continue;

    const words = sanitizeWords(record.words);
    const furigana = sanitizeFurigana(record.furigana, text);
    const translations = sanitizeTranslations(record.translations);
    const voice = typeof record.voice === 'string' && record.voice.trim() ? record.voice.trim() : null;

    lines.push({
      timestampMs,
      text,
      ...(words.length > 0 ? { words } : {}),
      ...(furigana.length > 0 ? { furigana } : {}),
      ...(translations.length > 0 ? { translations } : {}),
      ...(voice ? { voice } : {}),
    });
  }

  lines.sort((left, right) => left.timestampMs - right.timestampMs);
  return lines;
}

function normalizeParsedOffsetMs(file: XLRCFile): number {
  const offset = file.meta.offset;
  return typeof offset === 'number' && Number.isFinite(offset) ? Math.trunc(offset) : 0;
}

function applyParsedOffsetMs(timestampMs: number, offsetMs: number): number {
  return Math.max(0, Math.floor(timestampMs + offsetMs));
}

function mapParsedLine(line: XLRCLine, offsetMs: number, preserveRichFields: boolean): LyricsLine {
  const timestampMs = applyParsedOffsetMs(line.timestamp, offsetMs);
  const text = line.text.trim();
  if (line.isEmpty || !text) {
    return {
      timestampMs,
      text: '',
      kind: 'silence',
    };
  }

  if (!preserveRichFields) {
    return { timestampMs, text };
  }

  const words = line.words
    .map((word): LyricsWord => {
      const wordText = word.text;
      const wordFurigana = sanitizeFurigana(word.furigana, wordText);
      return {
        timestampMs: applyParsedOffsetMs(word.timestamp, offsetMs),
        text: wordText,
        ...(wordFurigana.length > 0 ? { furigana: wordFurigana } : {}),
      };
    })
    .filter((word) => word.text.trim().length > 0);
  const furigana = sanitizeFurigana(line.furigana, text);
  const translations = sanitizeTranslations(line.translations);
  const voice = line.voice?.trim() || null;

  return {
    timestampMs,
    text,
    ...(words.length > 0 ? { words } : {}),
    ...(furigana.length > 0 ? { furigana } : {}),
    ...(translations.length > 0 ? { translations } : {}),
    ...(voice ? { voice } : {}),
  };
}

function parsePackageSyncedLines(lyricsText: string, preserveRichFields: boolean): LyricsLine[] {
  const normalizedText = normalizeLyricsText(lyricsText);
  if (!normalizedText) return [];

  const parsed = parseXLRC(normalizedText);
  const offsetMs = normalizeParsedOffsetMs(parsed);
  return sanitizeLyricsLines(
    parsed.lines.map((line) => mapParsedLine(line, offsetMs, preserveRichFields))
  );
}

export function parseLrcSyncedLines(lyricsText: string): LyricsLine[] {
  return parsePackageSyncedLines(lyricsText, false);
}

function parseXlrcSyncedLines(lyricsText: string): LyricsLine[] {
  return parsePackageSyncedLines(lyricsText, true);
}

export function toPlainLyricsFromLines(lines: LyricsLine[]): string | null {
  const textLines = lines
    .filter((line) => line.kind !== 'silence' && line.text.trim().length > 0)
    .map((line) => line.text);
  if (textLines.length === 0) return null;
  return textLines.join('\n');
}

export function createLyricsPayload(
  source: LyricsPayload['source'],
  provider: LyricsPayload['provider'],
  format: LyricsFormat,
  plainLyrics: string | null,
  syncedLyrics: string | null,
  syncedLines: LyricsLine[]
): LyricsPayload | null {
  const normalizedPlain = normalizeLyricsText(plainLyrics);
  const normalizedSynced = normalizeLyricsText(syncedLyrics);
  const parsedSyncedLines =
    normalizedSynced && (format === 'lrc' || format === 'xlrc')
      ? parsePackageSyncedLines(normalizedSynced, format === 'xlrc')
      : [];
  const sourceLines = parsedSyncedLines.length > 0 ? parsedSyncedLines : syncedLines;
  const normalizedLines = sanitizeLyricsLines(sourceLines);

  if (!normalizedPlain && !normalizedSynced && normalizedLines.length === 0) {
    return null;
  }

  return {
    source,
    provider,
    format,
    plainLyrics: normalizedPlain ?? toPlainLyricsFromLines(normalizedLines),
    syncedLyrics: normalizedSynced ?? toPlainLyricsFromLines(normalizedLines),
    syncedLines: normalizedLines,
  };
}

function parseXlrcLyricsText(lyricsText: string, source: LyricsPayload['source']): LyricsPayload | null {
  const normalizedText = normalizeLyricsText(lyricsText);
  if (!normalizedText) return null;

  const syncedLines = parseXlrcSyncedLines(normalizedText);
  if (syncedLines.length === 0) return null;

  return createLyricsPayload(
    source,
    null,
    'xlrc',
    toPlainLyricsFromLines(syncedLines),
    normalizedText,
    syncedLines
  );
}

export function parseLyricsText(
  lyricsText: string,
  source: LyricsPayload['source'],
  format: LyricsFormat = 'lrc'
): LyricsPayload | null {
  const normalizedText = normalizeLyricsText(lyricsText);
  if (!normalizedText) return null;

  if (format === 'xlrc') {
    return parseXlrcLyricsText(normalizedText, source);
  }

  if (format === 'plain') {
    return createLyricsPayload(source, null, 'plain', normalizedText, null, []);
  }

  const syncedLines = parseLrcSyncedLines(normalizedText);
  const hasSyncedLyrics = syncedLines.length > 0;
  const syncedLyrics = hasSyncedLyrics ? normalizedText : null;
  const plainLyrics = hasSyncedLyrics
    ? toPlainLyricsFromLines(syncedLines) ?? normalizedText
    : normalizedText;

  return createLyricsPayload(source, null, hasSyncedLyrics ? 'lrc' : 'plain', plainLyrics, syncedLyrics, syncedLines);
}
