using NAudio.CoreAudioApi;
using NAudio.Wave;

namespace ytLive.Services.Audio;

/// <summary>
/// Captures the selected microphone (TASK 4 ship step 4) via WASAPI. Resolves
/// the NAudio device by FriendlyName matching <c>MicSourceName</c> (the app only
/// persists DisplayName), falling back to the default capture endpoint. The
/// device's own mix format is used — the mixer normalizes any rate to 48 kHz
/// (TASK 9) so no format forcing is needed here.
/// </summary>
public sealed class WasapiMicAudioSource : IAudioSource
{
    private readonly Func<string?> _micNameProvider;
    private WasapiCapture? _capture;

    /// <param name="micNameProvider">Returns the current mic DisplayName; read
    /// at each Start so a device picked mid-session takes effect immediately
    /// (the mixer restarts the mic on pick).</param>
    public WasapiMicAudioSource(Func<string?> micNameProvider)
    {
        _micNameProvider = micNameProvider;
    }

    public event Action? Started;
    public event Action<AudioSample>? SampleReady;
    public event Action<Exception>? Failed;

    public void Start()
    {
        if (_capture != null)
            throw new InvalidOperationException("Already started.");

        try
        {
            var device = ResolveDevice();
            _capture = device != null ? new WasapiCapture(device) : new WasapiCapture();
            _capture.DataAvailable += OnDataAvailable;
            _capture.RecordingStopped += OnRecordingStopped;
            _capture.StartRecording();
            Started?.Invoke();
        }
        catch (Exception ex)
        {
            Stop();
            Failed?.Invoke(ex);
        }
    }

    public void Stop()
    {
        if (_capture == null)
            return;

        try
        {
            _capture.StopRecording();
        }
        catch
        {
        }
        _capture.Dispose();
        _capture = null;
    }

    public void Dispose() => Stop();

    private MMDevice? ResolveDevice()
    {
        var micName = _micNameProvider();
        if (string.IsNullOrWhiteSpace(micName))
            return null;

        try
        {
            using var enumerator = new MMDeviceEnumerator();
            foreach (var endpoint in enumerator.EnumerateAudioEndPoints(DataFlow.Capture, DeviceState.Active))
            {
                if (string.Equals(endpoint.FriendlyName, micName, StringComparison.OrdinalIgnoreCase))
                    return endpoint;
            }
        }
        catch
        {
        }

        return null;
    }

    private void OnDataAvailable(object? sender, WaveInEventArgs e)
    {
        if (e.BytesRecorded <= 0)
            return;

        var format = _capture?.WaveFormat ?? WaveFormat.CreateIeeeFloatWaveFormat(48000, 2);
        var samples = WaveToFloat.Convert(e.Buffer, e.BytesRecorded, format);
        if (samples.Length > 0)
            SampleReady?.Invoke(new AudioSample(samples, format.SampleRate, format.Channels));
    }

    private void OnRecordingStopped(object? sender, StoppedEventArgs e)
    {
        if (e.Exception != null)
            Failed?.Invoke(e.Exception);
    }
}
