// PRE Web GUI — WebVTT (.vtt) parser
//
// Turns a raw WebVTT transcript into clean readable text the model can reason
// over. Drops the WEBVTT signature, NOTE/STYLE/REGION blocks, cue identifiers,
// and timestamp lines. Preserves speaker attribution from <v Speaker>...</v>
// tags and from "Speaker: text" patterns. Collapses consecutive cues by the
// same speaker so the output reads like a transcript instead of subtitle blocks.
//
// Reference: https://www.w3.org/TR/webvtt1/

/**
 * Detect whether a string of text looks like a WebVTT document.
 * The spec requires "WEBVTT" as the first line, optionally followed by a tab
 * or space and a description. We're lenient on BOM and surrounding whitespace.
 */
function looksLikeVtt(text) {
  if (!text || typeof text !== 'string') return false;
  // Strip UTF-8 BOM if present, then check the first non-empty line
  const first = text.replace(/^﻿/, '').trimStart().split(/\r?\n/, 1)[0] || '';
  return /^WEBVTT(\s|$)/.test(first);
}

// HTML/VTT tags inside cue payload that should be stripped without losing the
// inner text. <v Speaker>foo</v> is handled separately so we can keep the
// speaker name.
const INLINE_TAG = /<\/?(?:c|i|b|u|ruby|rt|lang|noscript|small|sub|sup|em|strong)(?:\.[^>]*)?(?:\s[^>]*)?>/gi;
// Inline timestamp anchors (used for karaoke-style highlighting):
//   <00:00:05.000>some text
const INLINE_TIMESTAMP = /<\d{2}:\d{2}:\d{2}\.\d{3}>/g;
// Voice tags carry speaker attribution:
//   <v Speaker Name>text</v>  or  <v.classname Speaker Name>text
const VOICE_TAG = /<v(?:\.[^\s>]+)?(?:\s+([^>]*))?>([^<]*)(?:<\/v>)?/gi;
// Cue timestamp line: "00:00:05.000 --> 00:00:08.000" possibly with settings
const TIMESTAMP_LINE = /^(?:\d{1,2}:)?\d{2}:\d{2}\.\d{3}\s+-->\s+(?:\d{1,2}:)?\d{2}:\d{2}\.\d{3}(\s.*)?$/;

/**
 * Parse a raw WebVTT string into clean readable text with optional speaker
 * attribution. Returns an object with the text and lightweight metadata so the
 * caller can render a header line.
 *
 * @param {string} raw - The .vtt file contents
 * @returns {{ text: string, cueCount: number, speakers: string[] }}
 */
function parseVtt(raw) {
  if (!raw) return { text: '', cueCount: 0, speakers: [] };

  // Normalize line endings and strip BOM up front so split() is well-behaved.
  const text = raw.replace(/^﻿/, '').replace(/\r\n/g, '\n').replace(/\r/g, '\n');
  const lines = text.split('\n');

  // State machine for walking the document. We don't need a full WebVTT parser
  // — just enough to skip metadata blocks and pull cue payloads.
  const cues = []; // each entry: { speaker: string|null, text: string }
  let i = 0;

  // Skip the WEBVTT signature and any header description lines until first blank
  while (i < lines.length && lines[i].trim() !== '') i++;
  // Now i points at the first blank line (or end). Skip it.
  while (i < lines.length && lines[i].trim() === '') i++;

  while (i < lines.length) {
    const line = lines[i];
    const trimmed = line.trim();

    if (trimmed === '') {
      i++;
      continue;
    }

    // NOTE / STYLE / REGION blocks — skip until next blank line
    if (/^(NOTE|STYLE|REGION)(\s|$)/.test(trimmed)) {
      i++;
      while (i < lines.length && lines[i].trim() !== '') i++;
      continue;
    }

    // A cue is identified by either:
    //   1. A line that *is* the timestamp line, or
    //   2. A cue identifier line followed by a timestamp line.
    let cueIdLine = null;
    if (!TIMESTAMP_LINE.test(trimmed)) {
      // Could be a cue identifier; peek ahead
      if (i + 1 < lines.length && TIMESTAMP_LINE.test(lines[i + 1].trim())) {
        cueIdLine = trimmed;
        i++;
      } else {
        // Not a valid cue start — skip this line (defensive against malformed files)
        i++;
        continue;
      }
    }

    // i now points at the timestamp line. Skip it.
    if (i < lines.length && TIMESTAMP_LINE.test(lines[i].trim())) {
      i++;
    }

    // Collect payload lines until the next blank line or EOF
    const payload = [];
    while (i < lines.length && lines[i].trim() !== '') {
      payload.push(lines[i]);
      i++;
    }

    if (payload.length === 0) continue;
    void cueIdLine; // cueIdLine is intentionally discarded — not useful for transcript text

    const { speaker, text: cleaned } = _extractSpeakerAndText(payload.join('\n'));
    if (cleaned) cues.push({ speaker, text: cleaned });
  }

  // Collapse consecutive cues by the same NAMED speaker into a single paragraph
  // so a transcript reads naturally. Unattributed cues (speaker === null) stay
  // as separate paragraphs — for a subtitle file we'd rather preserve cue
  // boundaries than blur them into one wall of text.
  const merged = [];
  for (const cue of cues) {
    const last = merged[merged.length - 1];
    if (last && last.speaker && last.speaker === cue.speaker) {
      last.text += ' ' + cue.text;
    } else {
      merged.push({ ...cue });
    }
  }

  const speakers = [...new Set(cues.map(c => c.speaker).filter(Boolean))];

  const formatted = merged.map(c => {
    if (c.speaker) return `${c.speaker}: ${c.text}`;
    return c.text;
  }).join('\n\n');

  return { text: formatted, cueCount: cues.length, speakers };
}

/**
 * Extract a speaker label and clean text from a cue payload. Tries (in order):
 *   1. <v Speaker>text</v> voice tag
 *   2. "Speaker Name: text" pattern at the start of the cue
 *   3. No speaker — return the cleaned text only
 */
function _extractSpeakerAndText(payload) {
  // Strip inline timestamp anchors and most HTML-like tags first, except <v>
  // which we need to inspect for speaker attribution.
  let body = payload;

  // Voice tag wins if present
  let speaker = null;
  const vMatch = VOICE_TAG.exec(body);
  VOICE_TAG.lastIndex = 0; // reset since we created the regex with /g
  if (vMatch) {
    speaker = (vMatch[1] || '').trim() || null;
    // Replace ALL voice tags with their inner text. We capture content inside
    // the opening <v ...> and the closing </v> if present; some authors omit
    // the closing tag, so we have to handle both.
    body = body.replace(VOICE_TAG, (_full, _spk, inner) => inner || '');
  }

  body = body.replace(INLINE_TIMESTAMP, '').replace(INLINE_TAG, '');
  body = body.replace(/\s+/g, ' ').trim();

  // "Speaker: text" pattern at the start (Zoom transcripts, etc.) — only if we
  // didn't already get a speaker from a voice tag.
  if (!speaker) {
    const m = body.match(/^([A-Z][\w .'-]{0,60}?):\s+(.+)$/s);
    if (m) {
      speaker = m[1].trim();
      body = m[2].trim();
    }
  }

  return { speaker, text: body };
}

module.exports = { parseVtt, looksLikeVtt };
