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

import fixClip from './fixClip';

describe('fixClip', () => {
  it('should return null for invalid input', () => {
    expect(fixClip(null)).toBe(null);
    expect(fixClip(undefined)).toBe(null);
    expect(fixClip('string')).toBe(null);
  });

  it('should return null for clip without id', () => {
    const input = {
      name: 'Clip',
      startBeats: 0,
      endBeats: 4,
    };

    expect(fixClip(input)).toBe(null);
  });

  it('should return null for clip with endBeats <= startBeats', () => {
    const input = {
      id: 'clip1',
      startBeats: 10,
      endBeats: 10,
      clipId: 'content1',
      uploadId: null,
    };

    expect(fixClip(input)).toBe(null);
  });

  it('should fix a valid clip in place', () => {
    const input = {
      id: 'clip1',
      streaming: false,
      name: 'My Clip',
      color: '#FF0000',
      transposition: 0,
      amplitude: 1.0,
      startBeats: 0,
      endBeats: 4,
      readStartBeats: 0,
      fadeInBeats: 0,
      fadeOutBeats: 0,
      mute: false,
      loop: {
        enabled: false,
        startBeats: 0,
        endBeats: 4,
      },
      warp: {
        awaitingAnalysis: false,
        speed: 1.0,
        enabled: true,
        markers: {},
      },
      clipId: 'content1',
      uploadId: null,
    };

    const result = fixClip(input);

    expect(result).toBe(input); // Referential equality
    expect(result?.id).toBe('clip1');
    expect(result?.clipId).toBe('content1');
  });

  it('should clamp transposition to valid range', () => {
    const tooHigh = fixClip({
      id: 'clip1',
      transposition: 50,
      startBeats: 0,
      endBeats: 4,
      clipId: 'content1',
      uploadId: null,
    });
    expect(tooHigh?.transposition).toBe(24);

    const tooLow = fixClip({
      id: 'clip1',
      transposition: -50,
      startBeats: 0,
      endBeats: 4,
      clipId: 'content1',
      uploadId: null,
    });
    expect(tooLow?.transposition).toBe(-24);
  });

  it('should generate random color if missing', () => {
    const result = fixClip({
      id: 'clip1',
      startBeats: 0,
      endBeats: 4,
      clipId: 'content1',
      uploadId: null,
    });

    expect(result?.color).toBeDefined();
    expect(typeof result?.color).toBe('string');
    expect(result?.color).toMatch(/^#[0-9A-F]{6}$/i);
  });

  it('should handle clipId/uploadId discriminated union', () => {
    // Both present - prefer clipId
    const both = fixClip({
      id: 'clip1',
      startBeats: 0,
      endBeats: 4,
      clipId: 'content1',
      uploadId: 'upload1',
    });
    expect(both?.clipId).toBe('content1');
    expect(both?.uploadId).toBe(null);

    // Only clipId
    const clipOnly = fixClip({
      id: 'clip1',
      startBeats: 0,
      endBeats: 4,
      clipId: 'content1',
      uploadId: null,
    });
    expect(clipOnly?.clipId).toBe('content1');
    expect(clipOnly?.uploadId).toBe(null);

    // Only uploadId
    const uploadOnly = fixClip({
      id: 'clip1',
      startBeats: 0,
      endBeats: 4,
      clipId: null,
      uploadId: 'upload1',
    });
    expect(uploadOnly?.clipId).toBe(null);
    expect(uploadOnly?.uploadId).toBe('upload1');

    // Neither - invalid
    const neither = fixClip({
      id: 'clip1',
      startBeats: 0,
      endBeats: 4,
      clipId: null,
      uploadId: null,
    });
    expect(neither).toBe(null);
  });

  it('should fix nested loop and warp objects', () => {
    const result = fixClip({
      id: 'clip1',
      startBeats: 0,
      endBeats: 4,
      clipId: 'content1',
      uploadId: null,
      loop: {
        enabled: true,
        startBeats: 0,
        endBeats: 2,
      },
      warp: {
        speed: 2.0,
        enabled: true,
        markers: { 0: 0, 1: 1 },
      },
    });

    expect(result?.loop.enabled).toBe(true);
    expect(result?.loop.startBeats).toBe(0);
    expect(result?.loop.endBeats).toBe(2);
    expect(result?.warp.speed).toBe(2.0);
    expect(result?.warp.markers).toEqual({ 0: 0, 1: 1 });
  });
});
