export const DEFAULT_WAVEFORM_PEAK_COUNT = 160;
export const DEFAULT_DETAILED_WAVEFORM_PEAK_COUNT = 800;
const MIN_DISPLAY_WAVEFORM_PEAKS = 120;
const MAX_DISPLAY_WAVEFORM_PEAKS = 180;
const MIN_DETAILED_WAVEFORM_PEAKS = 500;
const LONG_AUDIO_MIN_DETAILED_WAVEFORM_PEAKS = 600;
const MAX_DETAILED_WAVEFORM_PEAKS = 1000;
const NEAR_SILENCE_THRESHOLD = 1;

export type WaveformPeakSet = {
  waveformPeaks: number[];
  waveformPeaksPreview: number[];
  waveformPeaksDetailed: number[];
};

export const getDefaultWaveformPeakCount = (): number => {
  const configured = Number(process.env.AUDIO_WAVEFORM_PEAKS);
  if (
    Number.isInteger(configured) &&
    configured >= MIN_DISPLAY_WAVEFORM_PEAKS &&
    configured <= MAX_DISPLAY_WAVEFORM_PEAKS
  ) {
    return configured;
  }

  return DEFAULT_WAVEFORM_PEAK_COUNT;
};

const clampPeak = (value: number): number => Math.max(0, Math.min(100, Math.round(value)));

const clampSampleCount = (
  targetCount: number,
  fallback: number,
  min: number,
  max: number,
): number => {
  if (!Number.isInteger(targetCount) || targetCount < min || targetCount > max) {
    return fallback;
  }

  return targetCount;
};

const fallbackWaveform = (samples: number): number[] =>
  Array.from({ length: samples }, (_, index) => {
    const wave = Math.sin((index / Math.max(1, samples - 1)) * Math.PI * 6);
    const accent = index % 9 === 0 ? 18 : index % 5 === 0 ? 8 : 0;
    return clampPeak(34 + wave * 18 + accent);
  });

const scaleToRange = (values: number[]): number[] => {
  if (!values.length) {
    return [];
  }

  const sorted = [...values].sort((left, right) => left - right);
  const max = sorted[sorted.length - 1] ?? 0;
  if (max <= NEAR_SILENCE_THRESHOLD) {
    return values.map((value) => clampPeak(value));
  }

  const percentileIndex = Math.max(0, Math.min(sorted.length - 1, Math.floor(sorted.length * 0.95)));
  const scaleMax = Math.max(sorted[percentileIndex] ?? max, 1);

  return values.map((value) => clampPeak((Math.min(value, scaleMax) / scaleMax) * 100));
};

const readPcmSample = (
  buffer: Buffer,
  offset: number,
  bitsPerSample: number,
  audioFormat: number,
): number => {
  if (audioFormat === 3) {
    if (bitsPerSample === 32 && offset + 4 <= buffer.length) {
      return Math.max(-1, Math.min(1, buffer.readFloatLE(offset)));
    }
    if (bitsPerSample === 64 && offset + 8 <= buffer.length) {
      return Math.max(-1, Math.min(1, buffer.readDoubleLE(offset)));
    }
    return 0;
  }

  switch (bitsPerSample) {
    case 8:
      return (buffer.readUInt8(offset) - 128) / 128;
    case 16:
      return buffer.readInt16LE(offset) / 32768;
    case 24: {
      if (offset + 3 > buffer.length) {
        return 0;
      }
      let value = buffer.readUIntLE(offset, 3);
      if (value & 0x800000) {
        value |= 0xff000000;
      }
      return value / 8388608;
    }
    case 32:
      return buffer.readInt32LE(offset) / 2147483648;
    default:
      return 0;
  }
};

const generateWaveformPeaksFromWavBuffer = (buffer: Buffer, samples: number): number[] | null => {
  if (buffer.length < 44 || buffer.toString('ascii', 0, 4) !== 'RIFF' || buffer.toString('ascii', 8, 12) !== 'WAVE') {
    return null;
  }

  let offset = 12;
  let audioFormat = 0;
  let channels = 0;
  let bitsPerSample = 0;
  let dataOffset = 0;
  let dataSize = 0;

  while (offset + 8 <= buffer.length) {
    const chunkId = buffer.toString('ascii', offset, offset + 4);
    const chunkSize = buffer.readUInt32LE(offset + 4);
    const chunkDataOffset = offset + 8;

    if (chunkId === 'fmt ' && chunkDataOffset + 16 <= buffer.length) {
      audioFormat = buffer.readUInt16LE(chunkDataOffset);
      channels = buffer.readUInt16LE(chunkDataOffset + 2);
      bitsPerSample = buffer.readUInt16LE(chunkDataOffset + 14);
    } else if (chunkId === 'data') {
      dataOffset = chunkDataOffset;
      dataSize = Math.min(chunkSize, buffer.length - chunkDataOffset);
    }

    offset = chunkDataOffset + chunkSize + (chunkSize % 2);
  }

  const bytesPerSample = bitsPerSample / 8;
  const frameSize = bytesPerSample * channels;
  if (
    !dataOffset ||
    !dataSize ||
    !channels ||
    !Number.isInteger(bytesPerSample) ||
    frameSize <= 0 ||
    (audioFormat !== 1 && audioFormat !== 3)
  ) {
    return null;
  }

  const frameCount = Math.floor(dataSize / frameSize);
  if (frameCount <= 0) {
    return null;
  }

  const bucketSize = Math.max(1, Math.ceil(frameCount / samples));
  const peaks: number[] = [];

  for (let bucketStart = 0; bucketStart < frameCount; bucketStart += bucketSize) {
    const bucketEnd = Math.min(frameCount, bucketStart + bucketSize);
    let sumSquares = 0;
    let count = 0;

    for (let frameIndex = bucketStart; frameIndex < bucketEnd; frameIndex += 1) {
      const frameOffset = dataOffset + frameIndex * frameSize;
      for (let channel = 0; channel < channels; channel += 1) {
        const sampleOffset = frameOffset + channel * bytesPerSample;
        const value = readPcmSample(buffer, sampleOffset, bitsPerSample, audioFormat);
        sumSquares += value * value;
        count += 1;
      }
    }

    peaks.push(count ? Math.sqrt(sumSquares / count) * 100 : 0);
  }

  return normalizeWaveformPeaks(peaks, samples);
};

export const normalizeWaveformPeaks = (
  input: unknown[] | undefined,
  targetCount = getDefaultWaveformPeakCount(),
): number[] => {
  const samples = clampSampleCount(
    targetCount,
    DEFAULT_WAVEFORM_PEAK_COUNT,
    MIN_DISPLAY_WAVEFORM_PEAKS,
    MAX_DETAILED_WAVEFORM_PEAKS,
  );

  if (!input?.length) {
    return fallbackWaveform(samples);
  }

  const cleaned = input
    .map((value) => (typeof value === 'number' ? value : Number(value)))
    .filter((value) => Number.isFinite(value))
    .map((value) => clampPeak(Math.abs(value)));

  if (!cleaned.length) {
    return fallbackWaveform(samples);
  }

  if (cleaned.length === samples) {
    return scaleToRange(cleaned);
  }

  if (cleaned.length === 1) {
    return Array.from({ length: samples }, () => cleaned[0]);
  }

  const resampled: number[] = [];
  const sourceMaxIndex = cleaned.length - 1;
  const targetMaxIndex = samples - 1;

  for (let i = 0; i < samples; i += 1) {
    const sourceIndex = targetMaxIndex === 0 ? 0 : (i / targetMaxIndex) * sourceMaxIndex;
    const leftIndex = Math.floor(sourceIndex);
    const rightIndex = Math.min(sourceMaxIndex, Math.ceil(sourceIndex));
    const ratio = sourceIndex - leftIndex;
    const interpolated = cleaned[leftIndex] * (1 - ratio) + cleaned[rightIndex] * ratio;
    resampled.push(interpolated);
  }

  return scaleToRange(resampled);
};

export const generateWaveformPeaksFromBuffer = (
  buffer: Buffer,
  samples = getDefaultWaveformPeakCount(),
): number[] => {
  if (!buffer.length) {
    return normalizeWaveformPeaks([], samples);
  }

  const wavPeaks = generateWaveformPeaksFromWavBuffer(buffer, samples);
  if (wavPeaks) {
    return wavPeaks;
  }

  const chunkSize = Math.max(1, Math.ceil(buffer.length / samples));
  const peaks: number[] = [];

  for (let offset = 0; offset < buffer.length; offset += chunkSize) {
    const chunk = buffer.subarray(offset, Math.min(buffer.length, offset + chunkSize));
    let total = 0;
    let localPeak = 0;

    for (const byte of chunk) {
      const centered = Math.abs(byte - 128);
      total += centered;
      localPeak = Math.max(localPeak, centered);
    }

    const average = chunk.length ? total / chunk.length : 0;
    peaks.push(Math.max(average, localPeak * 0.65));
  }

  return normalizeWaveformPeaks(peaks, samples);
};

export const generateWaveformPeaksFromSeed = (
  seed: string,
  samples = getDefaultWaveformPeakCount(),
): number[] => {
  const source = seed.trim() || 'audio';
  let hash = 2166136261;

  for (let i = 0; i < source.length; i += 1) {
    hash ^= source.charCodeAt(i);
    hash = Math.imul(hash, 16777619);
  }

  const phase = (hash % 360) * (Math.PI / 180);
  const peaks: number[] = [];

  for (let i = 0; i < samples; i += 1) {
    const progress = i / Math.max(1, samples - 1);
    const primary = Math.sin(progress * Math.PI * 12 + phase) * 24;
    const secondary = Math.sin(progress * Math.PI * 37 + phase / 2) * 11;
    const accent = i % 11 === 0 ? 16 : i % 7 === 0 ? 8 : 0;
    peaks.push(42 + primary + secondary + accent);
  }

  return normalizeWaveformPeaks(peaks, samples);
};

export const getDetailedWaveformPeakCount = (durationSeconds?: number | null): number => {
  if (!Number.isFinite(durationSeconds) || !durationSeconds || durationSeconds <= 0) {
    return DEFAULT_DETAILED_WAVEFORM_PEAK_COUNT;
  }

  const byQuarterSecond = Math.ceil(durationSeconds / 0.25);
  const minimum = durationSeconds >= 120 ? LONG_AUDIO_MIN_DETAILED_WAVEFORM_PEAKS : MIN_DETAILED_WAVEFORM_PEAKS;
  return Math.max(minimum, Math.min(MAX_DETAILED_WAVEFORM_PEAKS, byQuarterSecond));
};

export const buildWaveformPeakSet = (
  input: unknown[] | undefined,
  durationSeconds?: number | null,
): WaveformPeakSet => {
  const preview = normalizeWaveformPeaks(input, getDefaultWaveformPeakCount());
  const detailed = normalizeWaveformPeaks(
    input?.length ? input : preview,
    getDetailedWaveformPeakCount(durationSeconds),
  );

  return {
    waveformPeaks: preview,
    waveformPeaksPreview: preview,
    waveformPeaksDetailed: detailed,
  };
};

export const generateWaveformPeakSetFromBuffer = (
  buffer: Buffer,
  durationSeconds?: number | null,
): WaveformPeakSet => {
  const detailed = generateWaveformPeaksFromBuffer(
    buffer,
    getDetailedWaveformPeakCount(durationSeconds),
  );
  const preview = normalizeWaveformPeaks(detailed, getDefaultWaveformPeakCount());

  return {
    waveformPeaks: preview,
    waveformPeaksPreview: preview,
    waveformPeaksDetailed: detailed,
  };
};

export const generateWaveformPeakSetFromSeed = (
  seed: string,
  durationSeconds?: number | null,
): WaveformPeakSet => {
  const detailed = generateWaveformPeaksFromSeed(seed, getDetailedWaveformPeakCount(durationSeconds));
  const preview = normalizeWaveformPeaks(detailed, getDefaultWaveformPeakCount());

  return {
    waveformPeaks: preview,
    waveformPeaksPreview: preview,
    waveformPeaksDetailed: detailed,
  };
};
