import type { AudioFrame, AudioFormat } from "./types.ts";
import type { RingCentralAudioTransform } from "./ringcentral-bridge.ts";

export function createPcm16ResamplerTransform(input: {
  telephonyFormat: AudioFormat;
  providerFormat: AudioFormat;
}): RingCentralAudioTransform {
  return {
    toProviderInput(frame) {
      return resampleFrame(frame, input.providerFormat);
    },
    toTelephonyOutput(frame) {
      return resampleFrame(frame, input.telephonyFormat);
    },
  };
}

export function resampleFrame(frame: AudioFrame, targetFormat: AudioFormat): AudioFrame {
  if (
    frame.format.codec === targetFormat.codec &&
    frame.format.sampleRateHz === targetFormat.sampleRateHz &&
    frame.format.channels === targetFormat.channels
  ) {
    return frame;
  }

  if (frame.format.codec !== "pcm16" || targetFormat.codec !== "pcm16") {
    throw new Error(`Unsupported audio transform: ${frame.format.codec} -> ${targetFormat.codec}`);
  }
  if (frame.format.channels !== 1 || targetFormat.channels !== 1) {
    throw new Error("Only mono PCM16 resampling is supported.");
  }

  return {
    data: resamplePcm16(frame.data, frame.format.sampleRateHz, targetFormat.sampleRateHz),
    format: targetFormat,
    timestampMs: frame.timestampMs,
  };
}

export function resamplePcm16(
  input: Uint8Array,
  sourceRateHz: number,
  targetRateHz: number,
): Uint8Array {
  if (sourceRateHz === targetRateHz) return input;
  if (input.length === 0) return input;
  if (input.length % 2 !== 0) {
    throw new Error("PCM16 input length must be even.");
  }

  const source = new DataView(input.buffer, input.byteOffset, input.byteLength);
  const sourceLength = input.byteLength / 2;
  const targetLength = Math.max(1, Math.round((sourceLength * targetRateHz) / sourceRateHz));
  const target = new Int16Array(targetLength);
  const ratio = sourceRateHz / targetRateHz;

  for (let i = 0; i < targetLength; i += 1) {
    const sourceIndex = i * ratio;
    const leftIndex = Math.floor(sourceIndex);
    const rightIndex = Math.min(leftIndex + 1, sourceLength - 1);
    const fraction = sourceIndex - leftIndex;
    const left = readPcm16(source, leftIndex);
    const right = readPcm16(source, rightIndex);
    target[i] = clampInt16(Math.round(left + (right - left) * fraction));
  }

  return new Uint8Array(target.buffer);
}

function readPcm16(view: DataView, sampleIndex: number): number {
  return view.getInt16(sampleIndex * 2, true);
}

function clampInt16(value: number): number {
  return Math.max(-32768, Math.min(32767, value));
}
