// Package audio is maven's shared audio types.
//
// Audio crosses two boundaries in maven, and only two:
//
//   - client → core (the PushToTalk payload over internal/voice): raw audio
//     bytes the client captured after wake-word + VAD (per the spec's "clients
//     do capture, server transcribes" decision). one clean blob per utterance.
//   - core → worker module (/internal/worker for stt + tts): raw bytes the
//     server ships to / from the transcription + synthesis modules. core is
//     the worker's client here; the worker never reaches back.
//
// Format is fixed across both surfaces at scaffold time: 16 kHz mono int16
// little-endian PCM. faster-whisper, vosk, silero, and piper all work with
// 16 kHz mono; picking one shape up-front keeps the wire a single format
// field rather than a negotiation. A different rate upstream (e.g. an 8 kHz
// phone codec) downmixes at the client before send, never on the server —
// resampling on the always-on box is wasted work.
//
// Bytes are raw PCM, NOT a container (no WAV header on the wire). The client
// strips/generates WAV headers locally so the server's worker modules get
// exactly the bytes their model expects — there is no useful reason to ship
// a 44-byte header through a length-prefixed JSON frame. internal/pcmwav
// (a tiny helper below) is the WAV ⇄ PCM pair the reference client uses.
package audio

// Format describes raw PCM audio. Fixed at scaffold time; the wire carries
// it but today only one value is meaningful. Future: a negotiated registry
// if a second codec lands (e.g. opus, for a low-bitrate phone PWA path).
type Format struct {
	SampleRate int    `json:"sample_rate"` // samples/sec; default 16000
	Channels   int    `json:"channels"`    // 1 = mono
	SampleBits int    `json:"sample_bits"` // 16 ⇒ int16 little-endian PCM
	Encoding   string `json:"encoding"`    // "pcm_s16le" is the only value today
}

// PCM16kMono — the canonical maven audio shape. faster-whisper, silero,
// vosk, piper all consume it. Set as the default at every seam; the wire
// carries the explicit fields so a second format doesn't need a protocol
// version bump when it lands, just a new value here.
var PCM16kMono = Format{
	SampleRate: 16000,
	Channels:   1,
	SampleBits: 16,
	Encoding:   "pcm_s16le",
}

// Audio — one blob. Bytes is raw PCM in Format (no container header). The
// caller that built it (client capture, TTS synthesis output, a stub) knows
// the duration from len(Bytes) / Format.bytesPerSample() / SampleRate.
type Audio struct {
	Format Format `json:"format"`
	Bytes  []byte `json:"bytes"` // raw PCM; base64 over the wire via worker/voice JSON marshal
}

// Duration returns the playback duration implied by Bytes + Format. Returns
// 0 for empty Bytes or an unknown encoding. A sanity helper, not a contract:
// callers that need the duration for display use this; callers that need it
// for real (e.g. cooldown math) read it from the facts table, not the audio.
func (a Audio) Duration() float64 {
	if len(a.Bytes) == 0 {
		return 0
	}
	if a.Format.SampleRate <= 0 || a.Format.Channels <= 0 || a.Format.SampleBits <= 0 {
		return 0
	}
	if a.Format.Encoding != "pcm_s16le" {
		return 0
	}
	bytesPerSample := a.Format.SampleBits / 8
	if bytesPerSample == 0 {
		return 0
	}
	samples := float64(len(a.Bytes)) / float64(bytesPerSample*a.Format.Channels)
	return samples / float64(a.Format.SampleRate)
}

// IsValid reports whether f is a format maven can route today. Returns true
// only for the single canonical shape; a value with a different encoding is
// refused at the seam rather than mis-routed to a model that expects
// something else.
func (f Format) IsValid() bool {
	return f.SampleRate == PCM16kMono.SampleRate &&
		f.Channels == PCM16kMono.Channels &&
		f.SampleBits == PCM16kMono.SampleBits &&
		f.Encoding == PCM16kMono.Encoding
}
