// src/utils/audioUtils.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { encode, decode, decodeAudioData } from './audioUtils';

describe('audioUtils', () => {
  describe('encode and decode', () => {
    it('should correctly encode a Uint8Array to a base64 string', () => {
      // "Hello" in UTF-8
      const bytes = new Uint8Array([72, 101, 108, 108, 111]);
      const expectedBase64 = 'SGVsbG8=';
      expect(encode(bytes)).toBe(expectedBase64);
    });

    it('should correctly decode a base64 string to a Uint8Array', () => {
      const base64String = 'SGVsbG8=';
      const expectedBytes = new Uint8Array([72, 101, 108, 108, 111]);
      expect(decode(base64String)).toEqual(expectedBytes);
    });

    it('should be inverse functions of each other', () => {
      const originalBytes = new Uint8Array([1, 2, 3, 255, 0, 128]);
      const encoded = encode(originalBytes);
      const decoded = decode(encoded);
      expect(decoded).toEqual(originalBytes);
    });

    it('should handle empty arrays correctly', () => {
      const emptyBytes = new Uint8Array([]);
      expect(encode(emptyBytes)).toBe('');
      expect(decode('')).toEqual(emptyBytes);
    });
  });

  describe('decodeAudioData', () => {
    // Mock the AudioContext and its methods for a Node.js environment
    const mockGetChannelData = vi.fn();
    const mockCreateBuffer = vi.fn(() => ({
      getChannelData: mockGetChannelData,
    }));

    const mockAudioContext = {
      createBuffer: mockCreateBuffer,
    } as unknown as AudioContext;

    beforeEach(() => {
      vi.clearAllMocks();
    });

    it('should call createBuffer with the correct parameters for mono audio', async () => {
      // 4 samples of 16-bit mono audio (8 bytes total)
      const pcmData = new Uint8Array([0, 1, 0, 2, 0, 3, 0, 4]);
      const sampleRate = 24000;
      const numChannels = 1;

      // Set up the mock for getChannelData to return a valid array.
      // This prevents the "Cannot set properties of undefined" error inside decodeAudioData.
      mockGetChannelData.mockReturnValue(new Float32Array(4));

      await decodeAudioData(pcmData, mockAudioContext, sampleRate, numChannels);

      // 4 samples (Int16) / 1 channel = 4 frames
      const expectedFrameCount = 4;
      expect(mockCreateBuffer).toHaveBeenCalledWith(numChannels, expectedFrameCount, sampleRate);
    });

    it('should correctly normalize mono PCM data into the AudioBuffer', async () => {
      // 16-bit PCM data for values 256 and -512
      const pcmData = new Uint8Array([0, 1, 0, 254]); // Little-endian: 256, -512
      const channelData = new Float32Array(2);
      mockGetChannelData.mockReturnValue(channelData);

      await decodeAudioData(pcmData, mockAudioContext, 24000, 1);

      expect(mockGetChannelData).toHaveBeenCalledWith(0);
      // Check that the values were correctly normalized to the [-1.0, 1.0] range
      expect(channelData[0]).toBeCloseTo(256 / 32768.0);
      expect(channelData[1]).toBeCloseTo(-512 / 32768.0);
    });

    it('should correctly de-interleave and normalize stereo PCM data', async () => {
      // 16-bit PCM data for two stereo frames: [L1, R1, L2, R2]
      // L1=256, R1=512, L2=-256, R2=-512
      const pcmData = new Uint8Array([0, 1, 0, 2, 0, 255, 0, 254]);
      const leftChannelData = new Float32Array(2);
      const rightChannelData = new Float32Array(2);

      // Mock getChannelData to return the correct channel array based on the channel index
      mockGetChannelData.mockImplementation((channel: number) => {
        return channel === 0 ? leftChannelData : rightChannelData;
      });

      await decodeAudioData(pcmData, mockAudioContext, 24000, 2);

      expect(mockGetChannelData).toHaveBeenCalledWith(0); // Left channel
      expect(mockGetChannelData).toHaveBeenCalledWith(1); // Right channel

      // Check left channel values
      expect(leftChannelData[0]).toBeCloseTo(256 / 32768.0);
      expect(leftChannelData[1]).toBeCloseTo(-256 / 32768.0);

      // Check right channel values
      expect(rightChannelData[0]).toBeCloseTo(512 / 32768.0);
      expect(rightChannelData[1]).toBeCloseTo(-512 / 32768.0);
    });
  });
});