import { describe, expect, it } from 'vitest';

import {
  addDurations,
  clampTime,
  roundSeconds,
  validateSourceRange,
} from '../../../src/media/duration';
import {
  formatFrameTimecode,
  formatTimecode,
  parseTimecode,
  parseTimecodeDetailed,
} from '../../../src/media/timecode';

describe('duration arithmetic', () => {
  it('adds durations without exposing common binary drift', () => {
    expect(addDurations([0.1, 0.2, 1.0000004])).toBe(1.3);
    expect(roundSeconds(-0.0000001, 3)).toBe(0);
  });

  it('rejects invalid durations and clamps preview times', () => {
    expect(() => addDurations([1, Number.NaN])).toThrow();
    expect(() => addDurations([-1])).toThrow();
    expect(clampTime(-2, 10)).toBe(0);
    expect(clampTime(12, 10)).toBe(10);
  });

  it('validates source in/out ranges and source bounds', () => {
    expect(validateSourceRange(1, 3, 5)).toEqual({
      valid: true,
      durationSeconds: 2,
      issues: [],
    });
    expect(validateSourceRange(3, 1, 5).issues[0]?.code).toBe(
      'source-out-before-in'
    );
    expect(validateSourceRange(1, 1, 5).issues[0]?.code).toBe('clip-too-short');
    expect(
      validateSourceRange(1, 6, 5).issues.some(
        (issue) => issue.code === 'source-out-after-duration'
      )
    ).toBe(true);
  });
});

describe('timecode parsing and formatting', () => {
  it.each([
    ['12.5', 12.5, undefined],
    ['01:02.500', 62.5, undefined],
    ['02:03:04.125', 7_384.125, undefined],
    ['00:00:10:12', 10.5, { frameRate: 24 }],
  ])('parses %s', (text, expected, options) => {
    expect(parseTimecode(text, options)).toBe(expected);
  });

  it('reports malformed components', () => {
    expect(parseTimecodeDetailed('01:60')).toEqual({
      ok: false,
      reason: 'seconds-out-of-range',
    });
    expect(parseTimecodeDetailed('00:00:01:10')).toEqual({
      ok: false,
      reason: 'frame-rate-required',
    });
    expect(parseTimecodeDetailed('-00:01', { allowNegative: false })).toEqual({
      ok: false,
      reason: 'negative-not-allowed',
    });
    expect(parseTimecode('anything')).toBeUndefined();
    expect(parseTimecode('1.5:02:03')).toBeUndefined();
  });

  it('formats carry, precision, negative values and nominal frames', () => {
    expect(formatTimecode(59.9996)).toBe('00:01:00.000');
    expect(
      formatTimecode(62.5, {
        decimalPlaces: 2,
        alwaysShowHours: false,
      })
    ).toBe('01:02.50');
    expect(formatTimecode(-1.25, { decimalPlaces: 2 })).toBe('-00:00:01.25');
    expect(formatFrameTimecode(10.5, 24)).toBe('00:00:10:12');
    expect(() => formatFrameTimecode(5, 0.5)).toThrow();
  });
});
