// Tests for the WebVTT parser in src/vtt.js. We feed real-world cue shapes
// (Zoom-style transcripts, voice tags, NOTE blocks) and assert the parser
// returns clean readable text with speaker attribution preserved.

const { parseVtt, looksLikeVtt } = require('../src/vtt');

describe('looksLikeVtt', () => {
  test('accepts the canonical signature', () => {
    expect(looksLikeVtt('WEBVTT\n\n1\n00:00:00.000 --> 00:00:01.000\nHello')).toBe(true);
  });

  test('accepts WEBVTT with description suffix', () => {
    expect(looksLikeVtt('WEBVTT - Meeting transcript\n\n')).toBe(true);
  });

  test('handles UTF-8 BOM and leading whitespace', () => {
    expect(looksLikeVtt('﻿WEBVTT\n')).toBe(true);
    expect(looksLikeVtt('  WEBVTT\n')).toBe(true);
  });

  test('rejects HTML and plain text', () => {
    expect(looksLikeVtt('<html><body>nope</body></html>')).toBe(false);
    expect(looksLikeVtt('Just some prose with no signature.')).toBe(false);
    expect(looksLikeVtt('')).toBe(false);
    expect(looksLikeVtt(null)).toBe(false);
  });
});

describe('parseVtt — basic cues', () => {
  test('strips signature, cue numbers, and timestamps', () => {
    const vtt = [
      'WEBVTT',
      '',
      '1',
      '00:00:00.000 --> 00:00:03.000',
      'Hello world.',
      '',
      '2',
      '00:00:03.500 --> 00:00:06.000',
      'Second line of text.',
      '',
    ].join('\n');
    const { text, cueCount, speakers } = parseVtt(vtt);
    expect(cueCount).toBe(2);
    expect(speakers).toEqual([]);
    expect(text).toBe('Hello world.\n\nSecond line of text.');
    expect(text).not.toMatch(/-->/);
    expect(text).not.toMatch(/^\d+$/m);
  });

  test('handles cues without identifier lines', () => {
    const vtt = [
      'WEBVTT',
      '',
      '00:00:00.000 --> 00:00:02.000',
      'No identifier here.',
      '',
    ].join('\n');
    expect(parseVtt(vtt).text).toBe('No identifier here.');
  });

  test('joins multi-line payloads within a single cue', () => {
    const vtt = [
      'WEBVTT',
      '',
      '00:00:00.000 --> 00:00:02.000',
      'Line one',
      'continues here.',
      '',
    ].join('\n');
    expect(parseVtt(vtt).text).toBe('Line one continues here.');
  });
});

describe('parseVtt — speaker attribution', () => {
  test('extracts speaker from <v Speaker> voice tag', () => {
    const vtt = [
      'WEBVTT',
      '',
      '00:00:00.000 --> 00:00:03.000',
      '<v Roger>Hello, this is Roger.</v>',
      '',
      '00:00:03.000 --> 00:00:06.000',
      '<v Jane>And this is Jane.</v>',
      '',
    ].join('\n');
    const { text, speakers } = parseVtt(vtt);
    expect(speakers).toEqual(['Roger', 'Jane']);
    expect(text).toBe('Roger: Hello, this is Roger.\n\nJane: And this is Jane.');
  });

  test('extracts speaker from <v.classname Speaker> voice tag', () => {
    const vtt = [
      'WEBVTT',
      '',
      '00:00:00.000 --> 00:00:02.000',
      '<v.first.loud Roger Bingham>I am Roger.</v>',
      '',
    ].join('\n');
    expect(parseVtt(vtt).speakers).toEqual(['Roger Bingham']);
  });

  test('extracts speaker from "Name: text" pattern (Zoom style)', () => {
    const vtt = [
      'WEBVTT',
      '',
      '1',
      '00:00:00.000 --> 00:00:03.000',
      'Chris Bradford: Welcome to the standup.',
      '',
      '2',
      '00:00:03.500 --> 00:00:06.000',
      'Alex Higgins: Thanks for setting this up.',
      '',
    ].join('\n');
    const { text, speakers } = parseVtt(vtt);
    expect(speakers).toEqual(['Chris Bradford', 'Alex Higgins']);
    expect(text).toContain('Chris Bradford: Welcome to the standup.');
    expect(text).toContain('Alex Higgins: Thanks for setting this up.');
  });

  test('collapses consecutive cues by the same speaker', () => {
    const vtt = [
      'WEBVTT',
      '',
      '00:00:00.000 --> 00:00:03.000',
      '<v Alice>First sentence.</v>',
      '',
      '00:00:03.000 --> 00:00:06.000',
      '<v Alice>Second sentence.</v>',
      '',
      '00:00:06.000 --> 00:00:09.000',
      '<v Bob>Bob speaks.</v>',
      '',
      '00:00:09.000 --> 00:00:12.000',
      '<v Alice>Alice returns.</v>',
      '',
    ].join('\n');
    const { text } = parseVtt(vtt);
    expect(text).toBe(
      'Alice: First sentence. Second sentence.\n\n' +
      'Bob: Bob speaks.\n\n' +
      'Alice: Alice returns.'
    );
  });
});

describe('parseVtt — metadata blocks', () => {
  test('skips NOTE blocks', () => {
    const vtt = [
      'WEBVTT',
      '',
      'NOTE This is a comment we should ignore.',
      'NOTE continued on a second line.',
      '',
      '00:00:00.000 --> 00:00:02.000',
      'Real content.',
      '',
    ].join('\n');
    const { text } = parseVtt(vtt);
    expect(text).toBe('Real content.');
    expect(text).not.toMatch(/NOTE/);
    expect(text).not.toMatch(/comment/);
  });

  test('skips STYLE blocks', () => {
    const vtt = [
      'WEBVTT',
      '',
      'STYLE',
      '::cue { color: red; }',
      '',
      '00:00:00.000 --> 00:00:02.000',
      'Real content.',
      '',
    ].join('\n');
    expect(parseVtt(vtt).text).toBe('Real content.');
  });

  test('skips REGION blocks', () => {
    const vtt = [
      'WEBVTT',
      '',
      'REGION',
      'id:speakerA',
      'width:40%',
      '',
      '00:00:00.000 --> 00:00:02.000',
      'Real content.',
      '',
    ].join('\n');
    expect(parseVtt(vtt).text).toBe('Real content.');
  });
});

describe('parseVtt — inline tag stripping', () => {
  test('removes formatting tags but keeps inner text', () => {
    const vtt = [
      'WEBVTT',
      '',
      '00:00:00.000 --> 00:00:02.000',
      'This is <b>bold</b> and <i>italic</i> text.',
      '',
    ].join('\n');
    expect(parseVtt(vtt).text).toBe('This is bold and italic text.');
  });

  test('removes inline timestamp anchors used for karaoke', () => {
    const vtt = [
      'WEBVTT',
      '',
      '00:00:00.000 --> 00:00:05.000',
      'Some <00:00:01.000>text <00:00:02.500>with anchors.',
      '',
    ].join('\n');
    expect(parseVtt(vtt).text).toBe('Some text with anchors.');
  });

  test('removes class tags <c.classname>', () => {
    const vtt = [
      'WEBVTT',
      '',
      '00:00:00.000 --> 00:00:02.000',
      '<c.loud>Yelling text</c> and normal.',
      '',
    ].join('\n');
    expect(parseVtt(vtt).text).toBe('Yelling text and normal.');
  });
});

describe('parseVtt — edge cases', () => {
  test('handles UTF-8 BOM at start of file', () => {
    const vtt = '﻿WEBVTT\n\n00:00:00.000 --> 00:00:01.000\nHello.\n';
    expect(parseVtt(vtt).text).toBe('Hello.');
  });

  test('normalizes Windows line endings', () => {
    const vtt = 'WEBVTT\r\n\r\n00:00:00.000 --> 00:00:01.000\r\nHello.\r\n';
    expect(parseVtt(vtt).text).toBe('Hello.');
  });

  test('returns empty for empty input', () => {
    expect(parseVtt('')).toEqual({ text: '', cueCount: 0, speakers: [] });
    expect(parseVtt(null)).toEqual({ text: '', cueCount: 0, speakers: [] });
  });

  test('handles header description on WEBVTT line', () => {
    const vtt = 'WEBVTT - Meeting transcript 2026-05-22\n\n00:00:00.000 --> 00:00:02.000\nContent.\n';
    expect(parseVtt(vtt).text).toBe('Content.');
  });

  test('handles realistic Zoom-style transcript', () => {
    const vtt = [
      'WEBVTT',
      '',
      '1',
      '00:00:01.500 --> 00:00:04.200',
      'Chris Bradford: Thanks everyone for joining.',
      '',
      '2',
      '00:00:04.500 --> 00:00:07.800',
      'Chris Bradford: Let\'s start with the agenda.',
      '',
      'NOTE Background noise on Alex\'s mic',
      '',
      '3',
      '00:00:08.000 --> 00:00:11.500',
      'Alex Higgins: Sounds good, the first item is the rollout plan.',
      '',
    ].join('\n');
    const { text, speakers, cueCount } = parseVtt(vtt);
    expect(cueCount).toBe(3);
    expect(speakers).toEqual(['Chris Bradford', 'Alex Higgins']);
    expect(text).toBe(
      'Chris Bradford: Thanks everyone for joining. Let\'s start with the agenda.\n\n' +
      'Alex Higgins: Sounds good, the first item is the rollout plan.'
    );
  });
});
