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

import fixTiming, { FollowTrackTiming, ManualTiming } from './fixTiming';

describe('fixTiming', () => {
  it('should return default manual timing for invalid input', () => {
    const result = fixTiming(null, []) as ManualTiming;
    expect(result.type).toBe('manual');
    expect(result.bps).toBe(2);
    expect(result.bpsAutomation).toEqual([]);
  });

  it('should fix manual timing in place', () => {
    const input = {
      type: 'manual',
      bps: 3,
      bpsAutomation: [],
    };

    const result = fixTiming(input, []) as ManualTiming;

    expect(result).toBe(input); // Referential equality
    expect(result.type).toBe('manual');
    expect(result.bps).toBe(3);
  });

  it('should fix manual timing with automation', () => {
    const input = {
      type: 'manual',
      bps: 2,
      bpsAutomation: [
        { beats: 0, value: 2, curve: 0 },
        { beats: 4, value: 3, curve: 0.5 },
      ],
    };

    const result = fixTiming(input, []) as ManualTiming;

    expect(result.type).toBe('manual');
    expect(result.bpsAutomation).toHaveLength(2);
    expect(result.bpsAutomation[0].beats).toBe(0);
    expect(result.bpsAutomation[0].value).toBe(2);
  });

  it('should reject negative bps values', () => {
    const result = fixTiming({ type: 'manual', bps: -1 }, []) as ManualTiming;
    expect(result.bps).toBe(2);
  });

  it('should filter out invalid automation points', () => {
    const input = {
      type: 'manual',
      bps: 2,
      bpsAutomation: [
        { beats: 0, value: 2, curve: 0 }, // Valid
        { beats: NaN, value: 3, curve: 0 }, // Invalid beats
        { beats: 4, value: Infinity, curve: 0 }, // Invalid value
        { beats: 8, value: 4, curve: -1 }, // Negative curve invalid
        { beats: 10, value: 3, curve: 0.5 }, // Valid
      ],
    };

    const result = fixTiming(input, []) as ManualTiming;

    // Should only keep valid points
    expect(result.bpsAutomation).toHaveLength(2);
    expect(result.bpsAutomation[0]).toEqual({ beats: 0, value: 2, curve: 0 });
    expect(result.bpsAutomation[1]).toEqual({
      beats: 10,
      value: 3,
      curve: 0.5,
    });
  });

  it('should fix follow-track timing with valid trackId', () => {
    const input = {
      type: 'follow-track',
      trackId: 'track1',
      fallbackBPS: 2.5,
    };

    const result = fixTiming(input, ['track1', 'track2']) as FollowTrackTiming;

    expect(result.type).toBe('follow-track');
    expect(result.trackId).toBe('track1');
    expect(result.fallbackBPS).toBe(2.5);
  });

  it('should convert to manual timing if trackId is invalid', () => {
    const input = {
      type: 'follow-track',
      trackId: 'nonexistent',
      fallbackBPS: 3,
    };

    const result = fixTiming(input, ['track1', 'track2']) as ManualTiming;

    expect(result.type).toBe('manual');
    expect(result.bps).toBe(3); // Uses fallbackBPS
    expect(result.bpsAutomation).toEqual([]);
  });

  it('should convert to manual timing if trackId is missing', () => {
    const input = {
      type: 'follow-track',
      trackId: null,
      fallbackBPS: 2,
    };

    const result = fixTiming(input, ['track1']) as ManualTiming;

    expect(result.type).toBe('manual');
    expect(result.bps).toBe(2);
  });

  it('should handle optional lockBPS field', () => {
    const withLock = fixTiming(
      { type: 'manual', lockBPS: true, bps: 2 },
      []
    ) as ManualTiming;
    expect(withLock.lockBPS).toBe(true);

    const withoutLock = fixTiming(
      { type: 'manual', bps: 2 },
      []
    ) as ManualTiming;
    expect(withoutLock.lockBPS).toBeUndefined();
  });
});
