// Audio helpers — decoding, silence detection, waveform peak generation.

import type { SilenceSegment } from '@/types';
import { uid } from '@/store/editor';

const ctxSingleton: { ctx: AudioContext | null } = { ctx: null };

export function getAudioContext(): AudioContext {
  if (!ctxSingleton.ctx) {
    // Safari fallback
    const Ctor =
      (window as unknown as { AudioContext?: typeof AudioContext; webkitAudioContext?: typeof AudioContext })
        .AudioContext ||
      (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext!;
    ctxSingleton.ctx = new Ctor();
  }
  return ctxSingleton.ctx;
}

export async function decodeAudioFile(file: File): Promise<AudioBuffer> {
  const buf = await file.arrayBuffer();
  const ctx = getAudioContext();
  return await ctx.decodeAudioData(buf.slice(0));
}

/**
 * Compute peak data for waveform rendering.
 * Returns an array of {min,max} values in [-1, 1].
 */
export function computePeaks(buffer: AudioBuffer, width: number): { min: number; max: number }[] {
  const channel = buffer.getChannelData(0);
  const len = channel.length;
  const samplesPerPixel = Math.max(1, Math.floor(len / width));
  const peaks: { min: number; max: number }[] = [];
  for (let i = 0; i < width; i++) {
    let min = 1.0;
    let max = -1.0;
    const start = i * samplesPerPixel;
    const end = Math.min(len, start + samplesPerPixel);
    for (let j = start; j < end; j++) {
      const v = channel[j];
      if (v < min) min = v;
      if (v > max) max = v;
    }
    peaks.push({ min: min === 1.0 ? 0 : min, max: max === -1.0 ? 0 : max });
  }
  return peaks;
}

export interface SilenceOptions {
  thresholdDb: number;
  minDurationMs: number;
  /** window size for RMS calculation in ms */
  windowMs?: number;
}

/**
 * Detect silent ranges in an AudioBuffer using RMS over short windows.
 * Returns absolute (timeline-relative) silence segments in seconds.
 */
export function detectSilence(
  buffer: AudioBuffer,
  trackId: string,
  opts: SilenceOptions,
): SilenceSegment[] {
  const windowMs = opts.windowMs ?? 10;
  const sampleRate = buffer.sampleRate;
  const windowSize = Math.max(1, Math.floor((sampleRate * windowMs) / 1000));
  const channel = buffer.getChannelData(0);

  // Linear threshold from dB. dB = 20 * log10(rms) -> rms = 10^(db/20).
  const linearThreshold = Math.pow(10, opts.thresholdDb / 20);

  const minDurationSamples = Math.floor((opts.minDurationMs / 1000) * sampleRate);

  const segments: SilenceSegment[] = [];
  let inSilence = false;
  let silenceStart = 0;

  for (let i = 0; i < channel.length; i += windowSize) {
    const end = Math.min(channel.length, i + windowSize);
    let sumSq = 0;
    for (let j = i; j < end; j++) {
      const v = channel[j];
      sumSq += v * v;
    }
    const rms = Math.sqrt(sumSq / (end - i));

    if (rms < linearThreshold) {
      if (!inSilence) {
        inSilence = true;
        silenceStart = i;
      }
    } else {
      if (inSilence) {
        const silenceEnd = i;
        if (silenceEnd - silenceStart >= minDurationSamples) {
          segments.push({
            id: uid(),
            trackId,
            start: silenceStart / sampleRate,
            end: silenceEnd / sampleRate,
          });
        }
        inSilence = false;
      }
    }
  }

  if (inSilence) {
    const silenceEnd = channel.length;
    if (silenceEnd - silenceStart >= minDurationSamples) {
      segments.push({
        id: uid(),
        trackId,
        start: silenceStart / sampleRate,
        end: silenceEnd / sampleRate,
      });
    }
  }

  return segments;
}

export function dbToLinear(db: number): number {
  return Math.pow(10, db / 20);
}