// Audio format configuration
export type AudioFormat = {
  format?: string
  sampleRate?: number
  channels?: number
}

// Default audio format for recordings and tone generation
export const DEFAULT_AUDIO_FORMAT = {
  format: "S16_LE",
  sampleRate: 16000,
  channels: 1,
} as const

// Device from ALSA listing
export type Device = {
  id: string // "default" or "plughw:1,0"
  card: number // ALSA card number
  device: number // ALSA device number
  label: string // Human-readable name
  type: "playback" | "capture"
}

// Playback control handle
export type Playback = {
  isPlaying: boolean
  stop: () => Promise<void>
  finished: () => Promise<void>
}

// Streaming playback handle
export type StreamingPlayback = {
  isPlaying: boolean
  write: (chunk: Uint8Array) => void
  stop: () => Promise<void>
  bufferEmptyFor: number // milliseconds since buffer became empty, 0 if not empty
}

// Streaming recording control handle
export type StreamingRecording = {
  isRecording: boolean
  stream: () => ReadableStream<Uint8Array>
  stop: () => Promise<void>
}

// File recording control handle
export type FileRecording = {
  isRecording: boolean
  stop: () => Promise<void>
}

const parseDeviceLine = (line: string, type: "playback" | "capture"): Device | undefined => {
  if (!line.startsWith("card ")) return undefined

  const match = line.match(/^card (\d+):\s+\w+\s+\[(.+?)\],\s+device (\d+):/)
  if (!match) return undefined

  const [, cardStr, label, deviceStr] = match

  if (!cardStr || !label || !deviceStr) return undefined

  const card = parseInt(cardStr)
  const device = parseInt(deviceStr)

  return {
    id: `plughw:${card},${device}`,
    card,
    device,
    label,
    type,
  }
}

const parseAlsaDevices = (output: string, type: "playback" | "capture"): Device[] => {
  return output
    .split("\n")
    .map((line) => parseDeviceLine(line, type))
    .filter((device) => device !== undefined)
}

export const listDevices = async (): Promise<Device[]> => {
  const playbackOutput = await Bun.$`aplay -l`.text()
  const captureOutput = await Bun.$`arecord -l`.text()

  const playback = parseAlsaDevices(playbackOutput, "playback")
  const capture = parseAlsaDevices(captureOutput, "capture")

  return [...playback, ...capture]
}

export const findDeviceByLabel = async (
  label: string,
  type?: "playback" | "capture"
): Promise<Device> => {
  const devices = await listDevices()
  const device = devices.find((d) => d.label === label && (!type || d.type === type))

  if (!device) {
    const typeStr = type ? ` (type: ${type})` : ""
    throw new Error(`Device not found: ${label}${typeStr}`)
  }

  return device
}

export const calculateRMS = (chunk: Uint8Array): number => {
  const samples = new Int16Array(chunk.buffer, chunk.byteOffset, chunk.byteLength / 2)
  let sum = 0

  for (const sample of samples) {
    sum += sample * sample
  }

  return Math.sqrt(sum / samples.length)
}

export const generateToneSamples = (
  frequencies: number[],
  sampleRate: number,
  durationSeconds: number
): Uint8Array => {
  const numSamples = Math.floor(sampleRate * durationSeconds)
  const buffer = new ArrayBuffer(numSamples * 2) // 2 bytes per S16_LE sample
  const samples = new Int16Array(buffer)

  for (let i = 0; i < numSamples; i++) {
    const t = i / sampleRate
    let value = 0

    // Mix all frequencies together
    for (const freq of frequencies) {
      value += Math.sin(2 * Math.PI * freq * t)
    }

    // Average and scale to Int16 range
    value = (value / frequencies.length) * 32767
    samples[i] = Math.round(value)
  }

  return new Uint8Array(buffer)
}

export const streamTone = async (
  stream: { write: (chunk: Uint8Array) => void; end: () => void },
  frequencies: number[],
  durationMs: number,
  format: Required<AudioFormat>
): Promise<void> => {
  const infinite = durationMs === Infinity
  const durationSeconds = durationMs / 1000

  // Continuous tone
  const samples = generateToneSamples(
    frequencies,
    format.sampleRate,
    infinite ? 1 : durationSeconds
  )

  if (infinite) {
    // Loop 1-second chunks forever
    while (true) {
      stream.write(samples)
      await Bun.sleep(1000)
    }
  } else {
    stream.write(samples)
    stream.end()
  }
}
