import { describe, it, expect } from 'vitest';
import type { Segment } from '../types';
import {
  toSrt,
  toVtt,
  toTxt,
  toMarkdown,
  toJson,
  formatTranscript,
  EXPORT_META,
  type ExportFormat,
  type TranscriptExport,
} from './index';

// A small fixture spanning a sub-minute, a multi-minute, and an hour-plus time.
const segments: Segment[] = [
  { start: 0, end: 2.5, text: 'Hello world.' },
  { start: 2.5, end: 65.25, text: '  Leading and trailing space.  ' },
  { start: 3661.123, end: 3663.5, text: 'After one hour.' },
];

describe('toSrt', () => {
  it('numbers cues sequentially starting at 1', () => {
    const out = toSrt(segments);
    const lines = out.split('\n');
    expect(lines[0]).toBe('1');
    // Cues are separated by a blank line; indices appear as standalone lines.
    expect(out).toContain('\n\n2\n');
    expect(out).toContain('\n\n3\n');
  });

  it('uses HH:MM:SS,mmm timecodes with a comma separator', () => {
    const out = toSrt(segments);
    expect(out).toContain('00:00:00,000 --> 00:00:02,500');
    expect(out).toContain('00:00:02,500 --> 00:01:05,250');
  });

  it('renders hour-plus timestamps correctly', () => {
    const out = toSrt(segments);
    // 3661.123s -> 01:01:01,123 ; 3663.5s -> 01:01:03,500
    expect(out).toContain('01:01:01,123 --> 01:01:03,500');
  });

  it('trims segment text', () => {
    const out = toSrt(segments);
    expect(out).toContain('Leading and trailing space.');
    expect(out).not.toContain('  Leading and trailing space.  ');
  });

  it('separates cues by exactly one blank line and has no trailing newline', () => {
    const out = toSrt([
      { start: 0, end: 1, text: 'a' },
      { start: 1, end: 2, text: 'b' },
    ]);
    expect(out).toBe('1\n00:00:00,000 --> 00:00:01,000\na\n\n2\n00:00:01,000 --> 00:00:02,000\nb');
  });

  it('returns empty string for empty input', () => {
    expect(toSrt([])).toBe('');
  });
});

describe('toVtt', () => {
  it('starts with the WEBVTT header followed by a blank line', () => {
    const out = toVtt(segments);
    expect(out.startsWith('WEBVTT\n\n')).toBe(true);
  });

  it('uses a "." millisecond separator in cues', () => {
    const out = toVtt(segments);
    expect(out).toContain('00:00:00.000 --> 00:00:02.500');
    expect(out).toContain('01:01:01.123 --> 01:01:03.500');
    // No SRT-style comma separators should appear.
    expect(out).not.toMatch(/\d{2}:\d{2}:\d{2},\d{3}/);
  });

  it('trims segment text', () => {
    const out = toVtt(segments);
    expect(out).toContain('Leading and trailing space.');
  });

  it('returns exactly the header for empty input', () => {
    expect(toVtt([])).toBe('WEBVTT\n\n');
  });
});

describe('toTxt', () => {
  it('emits one trimmed segment per line', () => {
    const out = toTxt(segments);
    expect(out).toBe('Hello world.\nLeading and trailing space.\nAfter one hour.');
  });

  it('contains no timestamp-like substrings', () => {
    const out = toTxt(segments);
    // No HH:MM:SS, no m:ss clocks, and no bracketed times.
    expect(out).not.toMatch(/\d{2}:\d{2}:\d{2}/);
    expect(out).not.toMatch(/\d+:\d{2}/);
    expect(out).not.toMatch(/\[/);
  });

  it('returns empty string for empty input', () => {
    expect(toTxt([])).toBe('');
  });
});

describe('toMarkdown', () => {
  it('renders bold clock prefixes using formatClock', () => {
    const out = toMarkdown(segments);
    // Prefix uses the segment START, formatted by formatClock.
    expect(out).toContain('**[0:00]** Hello world.');
    expect(out).toContain('**[0:02]** Leading and trailing space.');
    // Hour-plus start uses h:mm:ss.
    expect(out).toContain('**[1:01:01]** After one hour.');
  });

  it('prepends a "# {title}" heading when a title is provided', () => {
    const out = toMarkdown(segments, { title: 'My Talk' });
    expect(out.startsWith('# My Talk\n\n')).toBe(true);
    expect(out).toContain('**[0:00]** Hello world.');
  });

  it('omits the heading when no title is given', () => {
    const out = toMarkdown(segments);
    expect(out.startsWith('#')).toBe(false);
  });

  it('emits just the heading for empty input with a title', () => {
    expect(toMarkdown([], { title: 'Empty' })).toBe('# Empty');
  });

  it('returns empty string for empty input without a title', () => {
    expect(toMarkdown([])).toBe('');
  });
});

describe('toJson', () => {
  it('round-trips via JSON.parse with version, segments and durationSec', () => {
    const out = toJson(segments, { title: 'Talk' });
    const parsed = JSON.parse(out) as TranscriptExport;
    expect(parsed.version).toBe(1);
    expect(parsed.title).toBe('Talk');
    expect(parsed.segments).toEqual(segments);
    // durationSec is the last (max) end.
    expect(parsed.durationSec).toBe(3663.5);
  });

  it('computes durationSec as the max segment end, not necessarily the last', () => {
    const out = toJson([
      { start: 0, end: 10, text: 'a' },
      { start: 1, end: 4, text: 'b' },
    ]);
    expect((JSON.parse(out) as TranscriptExport).durationSec).toBe(10);
  });

  it('is pretty-printed with 2-space indentation', () => {
    const out = toJson(segments);
    expect(out).toContain('\n  "version": 1');
  });

  it('omits title when not provided', () => {
    const out = toJson(segments);
    const parsed = JSON.parse(out) as TranscriptExport;
    expect('title' in parsed).toBe(false);
  });

  it('yields durationSec 0 and empty segments for empty input', () => {
    const parsed = JSON.parse(toJson([])) as TranscriptExport;
    expect(parsed.durationSec).toBe(0);
    expect(parsed.segments).toEqual([]);
    expect(parsed.version).toBe(1);
  });
});

describe('formatTranscript dispatch', () => {
  const cases: ExportFormat[] = ['srt', 'vtt', 'txt', 'md', 'json'];

  it('dispatches to each formatter identically to calling it directly', () => {
    expect(formatTranscript(segments, 'srt')).toBe(toSrt(segments));
    expect(formatTranscript(segments, 'vtt')).toBe(toVtt(segments));
    expect(formatTranscript(segments, 'txt')).toBe(toTxt(segments));
    expect(formatTranscript(segments, 'md', { title: 'T' })).toBe(toMarkdown(segments, { title: 'T' }));
    expect(formatTranscript(segments, 'json', { title: 'T' })).toBe(toJson(segments, { title: 'T' }));
  });

  it('produces non-throwing output for every format on empty input', () => {
    for (const fmt of cases) {
      expect(() => formatTranscript([], fmt)).not.toThrow();
    }
    expect(formatTranscript([], 'srt')).toBe('');
    expect(formatTranscript([], 'vtt')).toBe('WEBVTT\n\n');
  });
});

describe('EXPORT_META', () => {
  it('has an entry with ext/mime/label for every format', () => {
    const formats: ExportFormat[] = ['srt', 'vtt', 'txt', 'md', 'json'];
    for (const fmt of formats) {
      const meta = EXPORT_META[fmt];
      expect(meta.ext).toBeTruthy();
      expect(meta.mime).toContain('/');
      expect(meta.label).toBeTruthy();
    }
  });

  it('maps the file extension to match the format key', () => {
    expect(EXPORT_META.srt.ext).toBe('srt');
    expect(EXPORT_META.vtt.ext).toBe('vtt');
    expect(EXPORT_META.json.mime).toBe('application/json');
  });
});
