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

import {
  getClipBeatsToClipContentSeconds,
  getClipBeatsToTimelineBeats,
  getClipContentSecondsToClipBeats,
  getTimelineBeatsToClipBeats,
  getTimelineBeatsToTimelineSeconds,
  getTimelineSecondsToTimelineBeats,
} from './timeMapping';

describe('TL beats to TL seconds', () => {
  describe('with 0 automation', () => {
    it('should map 0 beats to firstBeatSeconds seconds', () => {
      const mapping = getTimelineBeatsToTimelineSeconds({
        bps: 1,
        bpsAutomation: [],
        firstBeatSeconds: 5,
      });
      expect(mapping(0)).toEqual([5, 5]);
    });
    it('should apply slope correctly', () => {
      const mapping = getTimelineBeatsToTimelineSeconds({
        bps: 2,
        bpsAutomation: [],
        firstBeatSeconds: 0,
      });
      expect(mapping(2)).toEqual([1, 1]);
      expect(mapping(1)).toEqual([0.5, 0.5]);
      expect(mapping(0)).toEqual([0, 0]);
      expect(mapping(-1)).toEqual([-0.5, -0.5]);
      expect(mapping(-2)).toEqual([-1, -1]);
    });
    it('should apply slope and offset correctly', () => {
      const mapping = getTimelineBeatsToTimelineSeconds({
        bps: 2,
        bpsAutomation: [],
        firstBeatSeconds: 5,
      });
      expect(mapping(2)).toEqual([6, 6]);
      expect(mapping(1)).toEqual([5.5, 5.5]);
      expect(mapping(0)).toEqual([5, 5]);
      expect(mapping(-1)).toEqual([4.5, 4.5]);
      expect(mapping(-2)).toEqual([4, 4]);
    });
  });

  describe('with 1 automation point', () => {
    it('should map 0 beats to firstBeatSeconds seconds', () => {
      const mapping = getTimelineBeatsToTimelineSeconds({
        bps: 1,
        bpsAutomation: [{ beats: 1, value: 2, curve: 0 }],
        firstBeatSeconds: 5,
      });
      expect(mapping(0)).toEqual([5, 5]);
    });
    it('should apply slope correctly', () => {
      const mapping = getTimelineBeatsToTimelineSeconds({
        bps: 1,
        bpsAutomation: [{ beats: 1, value: 2, curve: 0 }],
        firstBeatSeconds: 0,
      });
      expect(mapping(2)).toEqual([1, 1]);
      expect(mapping(1)).toEqual([0.5, 0.5]);
      expect(mapping(0)).toEqual([0, 0]);
      expect(mapping(-1)).toEqual([-0.5, -0.5]);
      expect(mapping(-2)).toEqual([-1, -1]);
    });
    it('should apply slope and offset correctly', () => {
      const mapping = getTimelineBeatsToTimelineSeconds({
        bps: 1,
        bpsAutomation: [{ beats: 1, value: 2, curve: 0 }],
        firstBeatSeconds: 5,
      });
      expect(mapping(2)).toEqual([6, 6]);
      expect(mapping(1)).toEqual([5.5, 5.5]);
      expect(mapping(0)).toEqual([5, 5]);
      expect(mapping(-1)).toEqual([4.5, 4.5]);
      expect(mapping(-2)).toEqual([4, 4]);
    });
  });

  describe('with 2 automation points', () => {
    describe('for values before the first automation point', () => {
      it('carries the first automation point value', () => {
        const mapping = getTimelineBeatsToTimelineSeconds({
          bps: 1,
          bpsAutomation: [
            { beats: 0, value: 0.5, curve: 0 },
            { beats: 4, value: 2, curve: 0 },
          ],
          firstBeatSeconds: 0,
        });
        expect(mapping(-2)).toEqual([-4, -4]);
      });
    });
    describe('for values after the last automation point', () => {
      it('carries the first automation point value', () => {
        const mapping = getTimelineBeatsToTimelineSeconds({
          bps: 1,
          bpsAutomation: [
            { beats: 0, value: 0.5, curve: 0 },
            { beats: 4, value: 2, curve: 0 },
          ],
          firstBeatSeconds: 0,
        });
        // 6 = 4 beats at 0.5 bps (8 seconds) + 2 beats at 2 bps (1 second)
        expect(mapping(6)).toEqual([9, 9]);
      });
    });
    describe('for values between the automation points', () => {
      it('carries the last automation point value', () => {
        const mapping = getTimelineBeatsToTimelineSeconds({
          bps: 1,
          bpsAutomation: [
            { beats: 0, value: 0.5, curve: 0 },
            { beats: 4, value: 2, curve: 0 },
          ],
          firstBeatSeconds: 0,
        });
        expect(mapping(2)).toEqual([4, 4]);
      });
    });
  });
});

describe('TL seconds to TL beats', () => {
  describe('with 0 automation', () => {
    it('should map firstBeatSeconds seconds to 0 beats', () => {
      const mapping = getTimelineSecondsToTimelineBeats({
        bps: 1,
        bpsAutomation: [],
        firstBeatSeconds: 5,
      });
      expect(mapping(5)).toEqual([0, 0]);
    });
    it('should apply slope correctly', () => {
      const mapping = getTimelineSecondsToTimelineBeats({
        bps: 2,
        bpsAutomation: [],
        firstBeatSeconds: 0,
      });
      expect(mapping(1)).toEqual([2, 2]);
      expect(mapping(0.5)).toEqual([1, 1]);
      expect(mapping(0)).toEqual([0, 0]);
      expect(mapping(-0.5)).toEqual([-1, -1]);
      expect(mapping(-1)).toEqual([-2, -2]);
    });
    it('should apply slope and offset correctly', () => {
      const mapping = getTimelineSecondsToTimelineBeats({
        bps: 2,
        bpsAutomation: [],
        firstBeatSeconds: 5,
      });
      expect(mapping(6)).toEqual([2, 2]);
      expect(mapping(5.5)).toEqual([1, 1]);
      expect(mapping(5)).toEqual([0, 0]);
      expect(mapping(4.5)).toEqual([-1, -1]);
      expect(mapping(4)).toEqual([-2, -2]);
    });
  });

  describe('with 1 automation point', () => {
    it('should map firstBeatSeconds seconds to 0 beats', () => {
      const mapping = getTimelineSecondsToTimelineBeats({
        bps: 1,
        bpsAutomation: [{ beats: 1, value: 2, curve: 0 }],
        firstBeatSeconds: 5,
      });
      expect(mapping(5)).toEqual([0, 0]);
    });
    it('should apply slope correctly', () => {
      const mapping = getTimelineSecondsToTimelineBeats({
        bps: 1,
        bpsAutomation: [{ beats: 1, value: 2, curve: 0 }],
        firstBeatSeconds: 0,
      });
      expect(mapping(1)).toEqual([2, 2]);
      expect(mapping(0.5)).toEqual([1, 1]);
      expect(mapping(0)).toEqual([0, 0]);
      expect(mapping(-0.5)).toEqual([-1, -1]);
      expect(mapping(-1)).toEqual([-2, -2]);
    });
    it('should apply slope and offset correctly', () => {
      const mapping = getTimelineSecondsToTimelineBeats({
        bps: 1,
        bpsAutomation: [{ beats: 1, value: 2, curve: 0 }],
        firstBeatSeconds: 5,
      });
      expect(mapping(6)).toEqual([2, 2]);
      expect(mapping(5.5)).toEqual([1, 1]);
      expect(mapping(5)).toEqual([0, 0]);
      expect(mapping(4.5)).toEqual([-1, -1]);
      expect(mapping(4)).toEqual([-2, -2]);
    });
  });

  describe('with 2 automation points', () => {
    describe('for values before the first automation point', () => {
      it('carries the first automation point value', () => {
        const mapping = getTimelineSecondsToTimelineBeats({
          bps: 1,
          bpsAutomation: [
            { beats: 0, value: 0.5, curve: 0 },
            { beats: 4, value: 2, curve: 0 },
          ],
          firstBeatSeconds: 0,
        });
        expect(mapping(-4)).toEqual([-2, -2]);
      });
    });
    describe('for values after the last automation point', () => {
      it('carries the first automation point value', () => {
        const mapping = getTimelineSecondsToTimelineBeats({
          bps: 1,
          bpsAutomation: [
            { beats: 0, value: 0.5, curve: 0 },
            { beats: 4, value: 2, curve: 0 },
          ],
          firstBeatSeconds: 0,
        });
        // 6 = 4 beats at 0.5 bps (8 seconds) + 2 beats at 2 bps (1 second)
        expect(mapping(9)).toEqual([6, 6]);
      });
    });
    describe('for values between the automation points', () => {
      it('carries the last automation point value', () => {
        const mapping = getTimelineSecondsToTimelineBeats({
          bps: 1,
          bpsAutomation: [
            { beats: 0, value: 0.5, curve: 0 },
            { beats: 4, value: 2, curve: 0 },
          ],
          firstBeatSeconds: 0,
        });
        expect(mapping(4)).toEqual([2, 2]);
      });
    });
  });
});

describe('TL beats to clip beats', () => {
  describe('with no loop', () => {
    const mapping = getTimelineBeatsToClipBeats({
      readStartBeats: 5,
      loop: { enabled: false, startBeats: 0, endBeats: 10 },
      startBeats: 0,
      endBeats: 10,
    });
    it('should map 0 beats to readStartBeats', () => {
      expect(mapping(0)).toEqual([5, 5]);
    });
    it('should map 10 beats to readStartBeats + 10', () => {
      expect(mapping(10)).toEqual([15, 15]);
    });
  });

  describe('with loop', () => {
    const mapping = getTimelineBeatsToClipBeats({
      readStartBeats: 5,
      loop: { enabled: true, startBeats: 0, endBeats: 10 },
      startBeats: 0,
      endBeats: 40,
    });

    it('should map 0 beats to readStartBeats', () => {
      expect(mapping(0)).toEqual([5, 5]);
    });
    it('should map 10 beats to the appropriate value in the loop', () => {
      expect(mapping(10)).toEqual([5, 5]);
    });
    it('should map 22 beats to the appropriate value in the loop', () => {
      expect(mapping(22)).toEqual([7, 7]);
    });
    it('should map 5 beats to the both loop boundaries', () => {
      expect(mapping(5)).toEqual([10, 0]);
    });
  });
});

describe('clip beats to TL beats', () => {
  describe('with no loop', () => {
    const mapping = getClipBeatsToTimelineBeats({
      readStartBeats: 5,
      loop: { enabled: false, startBeats: 0, endBeats: 10 },
      startBeats: 0,
      endBeats: 10,
    });
    it('should map readStartBeats to 0 beats', () => {
      expect(mapping(5)).toEqual([0, 0]);
    });
    it('should map readStartBeats + 10 to 10 beats', () => {
      expect(mapping(15)).toEqual([10, 10]);
    });
  });

  describe('with loop', () => {
    const mapping = getClipBeatsToTimelineBeats({
      readStartBeats: 5,
      loop: { enabled: true, startBeats: 0, endBeats: 10 },
      startBeats: 0,
      endBeats: 40,
    });

    it('should map readStartBeats to 0 beats', () => {
      expect(mapping(5)).toEqual([0, 0]);
    });
    it('should map the appropriate value in the loop to 2 beats', () => {
      expect(mapping(7)).toEqual([2, 2]);
    });
  });
});

describe('clip beats to clip content seconds', () => {
  describe('with no warp', () => {
    const mapping = getClipBeatsToClipContentSeconds(
      {
        warp: {
          speed: 1,
          awaitingAnalysis: false,
          enabled: false,
          markers: {},
        },
        transposition: 0,
      },
      10
    );
    it('should map 0 beats to 0 seconds', () => {
      expect(mapping(0)).toEqual([0, 0]);
    });
    it('should map 10 beats to 10 seconds', () => {
      expect(mapping(10)).toEqual([10, 10]);
    });
  });
  describe('with 1 warp marker', () => {
    const mapping = getClipBeatsToClipContentSeconds(
      {
        warp: {
          speed: 1,
          awaitingAnalysis: false,
          enabled: true,
          markers: { 1: 10 },
        },
        transposition: 0,
      },
      10
    );
    it('should map 9 beats to 0 seconds', () => {
      expect(mapping(9)).toEqual([0, 0]);
    });
    it('should map 0 beats to 0', () => {
      expect(mapping(0)).toEqual([0, 0]);
    });
  });
  describe('with 2 warp markers', () => {
    const mapping = getClipBeatsToClipContentSeconds(
      {
        warp: {
          speed: 1,
          awaitingAnalysis: false,
          enabled: true,
          markers: { 1: 10, 2: 20 },
        },
        transposition: 0,
      },
      10
    );
    it('should map 9 beats to 0.9 seconds', () => {
      expect(mapping(9)).toEqual([0.9, 0.9]);
    });
    it('should map 15 beats to 1.5 seconds', () => {
      expect(mapping(15)).toEqual([1.5, 1.5]);
    });
  });
});

describe('clip content seconds to clip beats', () => {
  describe('with no warp', () => {
    const mapping = getClipContentSecondsToClipBeats(
      {
        warp: {
          speed: 1,
          awaitingAnalysis: false,
          enabled: false,
          markers: {},
        },
        transposition: 0,
      },
      10
    );
    it('should map 0 seconds to 0 beats', () => {
      expect(mapping(0)).toEqual([0, 0]);
    });
    it('should map 10 seconds to 10 beats', () => {
      expect(mapping(10)).toEqual([10, 10]);
    });
  });
  describe('with 1 warp marker', () => {
    const mapping = getClipContentSecondsToClipBeats(
      {
        warp: {
          speed: 1,
          awaitingAnalysis: false,
          enabled: true,
          markers: { 1: 10 },
        },
        transposition: 0,
      },
      10
    );
    it('should map 0 seconds to 9 beats', () => {
      expect(mapping(0)).toEqual([9, 9]);
    });
    it('should map 10 seconds to 19 beats', () => {
      expect(mapping(10)).toEqual([19, 19]);
    });
  });
  describe('with 2 warp markers', () => {
    const mapping = getClipContentSecondsToClipBeats(
      {
        warp: {
          speed: 1,
          awaitingAnalysis: false,
          enabled: true,
          markers: { 1: 10, 2: 20 },
        },
        transposition: 0,
      },
      10
    );
    it('should map 0.9 seconds to 9 beats', () => {
      expect(mapping(0.9)).toEqual([9, 9]);
    });
    it('should map 1.5 seconds to 15 beats', () => {
      expect(mapping(1.5)).toEqual([15, 15]);
    });
  });
});
