//go:build windows

package audio

import (
	"fmt"
	"sync"
)

// Manager owns the DVK record/playback lifecycle: at most one recording and
// one playback at a time. Device ids are passed per call so the host can route
// recording to the mic and playback to the rig (or the preview speakers).
type Manager struct {
	mu       sync.Mutex
	recStop  chan struct{}
	recDone  chan recResult
	playStop chan struct{}
	monStop  chan struct{} // RX monitor passthrough (capture → render)
	monRing  *pcmRing      // live audio hand-off, also fed by the network stream
	txStop   chan struct{} // TX audio passthrough (mic → rig)
	onChange func()        // fired on any record/playback state transition
}

type recResult struct {
	pcm []byte
	err error
}

// NewManager creates a DVK manager. onChange (optional) is called whenever the
// recording/playback state changes, so the host can push an audio:status event.
func NewManager(onChange func()) *Manager { return &Manager{onChange: onChange} }

func (m *Manager) notify() {
	if m.onChange != nil {
		m.onChange()
	}
}

// StartRecording begins capturing from deviceID into memory. Finish with
// StopRecording (which writes the WAV) or CancelRecording (which discards it).
func (m *Manager) StartRecording(deviceID string) error {
	m.mu.Lock()
	if m.recStop != nil {
		m.mu.Unlock()
		return fmt.Errorf("already recording")
	}
	stop := make(chan struct{})
	done := make(chan recResult, 1)
	m.recStop, m.recDone = stop, done
	m.mu.Unlock() // release BEFORE notify — onChange re-enters via IsRecording()
	go func() {
		pcm, err := recordPCM(deviceID, stop)
		done <- recResult{pcm, err}
	}()
	m.notify()
	return nil
}

// StopRecording ends the capture and writes it to path as a WAV file.
func (m *Manager) StopRecording(path string) error {
	m.mu.Lock()
	stop, done := m.recStop, m.recDone
	m.recStop, m.recDone = nil, nil
	m.mu.Unlock()
	if stop == nil {
		return fmt.Errorf("not recording")
	}
	close(stop)
	res := <-done
	m.notify()
	if res.err != nil {
		return res.err
	}
	if len(res.pcm) == 0 {
		return fmt.Errorf("captured no audio (check the recording device)")
	}
	return writeWAV(path, res.pcm)
}

// CancelRecording aborts a recording without saving.
func (m *Manager) CancelRecording() {
	m.mu.Lock()
	stop, done := m.recStop, m.recDone
	m.recStop, m.recDone = nil, nil
	m.mu.Unlock()
	if stop != nil {
		close(stop)
		<-done
		m.notify()
	}
}

func (m *Manager) IsRecording() bool {
	m.mu.Lock()
	defer m.mu.Unlock()
	return m.recStop != nil
}

func (m *Manager) IsPlaying() bool {
	m.mu.Lock()
	defer m.mu.Unlock()
	return m.playStop != nil
}

// Play renders a WAV file to deviceID. Any current playback is stopped first.
// Returns immediately; playback runs in the background.
func (m *Manager) Play(deviceID, path string) error {
	pcm, rate, ch, bits, err := readWAV(path)
	if err != nil {
		return err
	}
	m.StopPlayback()
	stop := make(chan struct{})
	m.mu.Lock()
	m.playStop = stop
	m.mu.Unlock()
	go func() {
		_ = playPCM(deviceID, pcm, rate, ch, bits, stop)
		m.mu.Lock()
		if m.playStop == stop {
			m.playStop = nil
		}
		m.mu.Unlock()
		m.notify()
	}()
	m.notify()
	return nil
}

// StopPlayback halts any in-progress playback.
func (m *Manager) StopPlayback() {
	m.mu.Lock()
	stop := m.playStop
	m.playStop = nil
	m.mu.Unlock()
	if stop != nil {
		close(stop)
		m.notify()
	}
}

// ---- RX audio monitor (Phase 2: USB codec passthrough) --------------------
//
// StartMonitor pipes live RX audio from inputDev (e.g. the rig's "USB Audio
// CODEC" capture endpoint) to outputDev (your speakers/headset) through a
// latency-bounded ring, so you HEAR the radio inside OpsLog. The very same ring
// is later fed by the network 50003 stream instead of a USB capture — the render
// half is transport-agnostic. inputDev "" = system default capture.
func (m *Manager) StartMonitor(inputDev, outputDev string) error {
	return m.startMonitor(inputDev, outputDev, true)
}

// StartMonitorSink starts ONLY the render side (no USB capture) so an external
// producer — the network 50003 stream — can feed decoded RX PCM via
// PushMonitorAudio. Same output path as StartMonitor, minus the capture goroutine.
func (m *Manager) StartMonitorSink(outputDev string) error {
	return m.startMonitor("", outputDev, false)
}

// startMonitor wires the RX monitor: always a render loop pulling from monRing;
// when capture is true it also captures inputDev into that ring (USB monitor).
// When false the ring is fed only by PushMonitorAudio (network audio).
func (m *Manager) startMonitor(inputDev, outputDev string, capture bool) error {
	m.mu.Lock()
	if m.monStop != nil {
		m.mu.Unlock()
		return fmt.Errorf("monitor already running")
	}
	stop := make(chan struct{})
	ring := newPCMRing(bytesPerSec / 2) // ~500 ms cap — low latency for live monitor
	m.monStop, m.monRing = stop, ring
	m.mu.Unlock()

	if capture {
		// Producer: capture the rig's USB audio into the ring.
		go func() {
			_ = captureStream(inputDev, stop, func(chunk []byte) { ring.Push(chunk) })
		}()
	}
	// Consumer: render the ring to the output device at the internal 16 kHz mono.
	go func() {
		_ = renderStream(outputDev, sampleRate, channels, bitsPerSample, stop, ring)
	}()
	m.notify()
	return nil
}

// StopMonitor stops the RX monitor passthrough.
func (m *Manager) StopMonitor() {
	m.mu.Lock()
	stop := m.monStop
	m.monStop, m.monRing = nil, nil
	m.mu.Unlock()
	if stop != nil {
		close(stop)
		m.notify()
	}
}

// MonitorActive reports whether the RX monitor passthrough is running.
func (m *Manager) MonitorActive() bool {
	m.mu.Lock()
	defer m.mu.Unlock()
	return m.monStop != nil
}

// PushMonitorAudio feeds externally-sourced PCM (16 kHz mono 16-bit) into the
// active monitor's output — the hook the network 50003 audio stream uses to play
// decoded RX through the very same output path a USB capture feeds. No-op when no
// monitor is running. Keeps the unexported ring inside the package.
func (m *Manager) PushMonitorAudio(pcm []byte) {
	m.mu.Lock()
	ring := m.monRing
	m.mu.Unlock()
	if ring != nil {
		ring.Push(pcm)
	}
}

// ---- TX audio passthrough (Phase 3: live mic → rig over USB) --------------
//
// StartTXAudio pipes your live microphone (micDev) into the rig's audio input
// (toRadioDev — for a USB-connected rig, its "USB Audio CODEC" render endpoint),
// so you talk through the PC. It is the mirror of StartMonitor (same ring +
// capture + render primitives, source/sink swapped). PTT keying is the caller's
// job (the app layer keys PTT before this and unkeys after) so this stays a pure
// audio route. The captured 16 kHz mono stream is also the exact shape the future
// network 50003 TX will encode and send — so Phase 5 reuses this capture side.
func (m *Manager) StartTXAudio(micDev, toRadioDev string) error {
	m.mu.Lock()
	if m.txStop != nil {
		m.mu.Unlock()
		return fmt.Errorf("TX audio already running")
	}
	stop := make(chan struct{})
	ring := newPCMRing(bytesPerSec / 4) // ~250 ms — tighter for live TX latency
	m.txStop = stop
	m.mu.Unlock()

	go func() {
		_ = captureStream(micDev, stop, func(chunk []byte) { ring.Push(chunk) })
	}()
	go func() {
		_ = renderStream(toRadioDev, sampleRate, channels, bitsPerSample, stop, ring)
	}()
	m.notify()
	return nil
}

// StopTXAudio stops the TX mic→rig passthrough.
func (m *Manager) StopTXAudio() {
	m.mu.Lock()
	stop := m.txStop
	m.txStop = nil
	m.mu.Unlock()
	if stop != nil {
		close(stop)
		m.notify()
	}
}

// TXAudioActive reports whether the TX mic→rig passthrough is running.
func (m *Manager) TXAudioActive() bool {
	m.mu.Lock()
	defer m.mu.Unlock()
	return m.txStop != nil
}
