using ytLive.Services;

namespace ytLive.Services.Audio;

/// <summary>
/// Owns the mic + desktop/game capture sources (TASK 4 ship step 4) and drives
/// the footer meters. Capture runs for the app's lifetime (started once at
/// startup, stopped on shutdown) so both bars stay live in preview: mic samples
/// are level-metered and forwarded, loopback samples feed the game bar's meter.
/// The mixer surfaces mic connection state (Connected/Failed) for the status
/// dot and can restart the mic source mid-session when a device is re-picked.
/// </summary>
public sealed class AudioMixer : IDisposable
{
    private readonly IAudioSource _mic;
    private readonly IAudioSource _loopback;
    private readonly AudioLevelMeter _meter;
    private readonly AudioLevelMeter _loopbackMeter;
    private readonly Action<string>? _log;
    private bool _started;

    public AudioMixer(IAudioSource mic, IAudioSource loopback, Action<string>? log = null)
    {
        _mic = mic;
        _loopback = loopback;
        _meter = new AudioLevelMeter();
        _loopbackMeter = new AudioLevelMeter();
        _log = log;

        _mic.Started += OnMicStarted;
        _mic.SampleReady += OnMicSample;
        _loopback.SampleReady += OnLoopbackSample;
        _mic.Failed += OnMicFailed;
        _loopback.Failed += OnLoopbackFailed;
    }

    /// <summary>Current smoothed mic level (0..1).</summary>
    public float MicLevel => _meter.Level;

    /// <summary>Current smoothed desktop/game level (0..1).</summary>
    public float LoopbackLevel => _loopbackMeter.Level;

    /// <summary>Raised whenever the smoothed mic level changes.</summary>
    public event Action<float>? MicLevelChanged;

    /// <summary>Raised whenever the smoothed desktop/game level changes.</summary>
    public event Action<float>? LoopbackLevelChanged;

    /// <summary>Raised when the mic capture comes up (the status dot goes green).</summary>
    public event Action? MicConnected;

    /// <summary>Raised when the mic capture fails or dies (the status dot goes yellow).</summary>
    public event Action<Exception>? MicFailed;

    public void Start()
    {
        if (_started)
            return;

        _started = true;
        _meter.Reset();
        _loopback.Start();
        _mic.Start();
    }

    /// <summary>Swaps the mic source without touching loopback — used when the
    /// creator picks a different device mid-session. The level resets and the
    /// new source raises <see cref="MicConnected"/> or <see cref="MicFailed"/>.</summary>
    public void RestartMic()
    {
        _mic.Stop();
        _meter.Reset();
        MicLevelChanged?.Invoke(0);
        _mic.Start();
    }

    public void Stop()
    {
        if (!_started)
            return;

        _started = false;
        _mic.Stop();
        _loopback.Stop();
        _meter.Reset();
        _loopbackMeter.Reset();
        MicLevelChanged?.Invoke(0);
        LoopbackLevelChanged?.Invoke(0);
    }

    public void Dispose()
    {
        Stop();
        _mic.Started -= OnMicStarted;
        _mic.SampleReady -= OnMicSample;
        _loopback.SampleReady -= OnLoopbackSample;
        _mic.Failed -= OnMicFailed;
        _loopback.Failed -= OnLoopbackFailed;
        _mic.Dispose();
        _loopback.Dispose();
    }

    private void OnMicStarted()
    {
        MicConnected?.Invoke();
    }

    private void OnMicSample(AudioSample sample)
    {
        // Push unconditionally: the ?. on the event would otherwise skip the
        // argument (and the meter update) when nothing is subscribed yet.
        var level = _meter.Push(sample);
        MicLevelChanged?.Invoke(level);
    }

    private void OnLoopbackSample(AudioSample sample)
    {
        var level = _loopbackMeter.Push(sample);
        LoopbackLevelChanged?.Invoke(level);
    }

    private void OnMicFailed(Exception ex)
    {
        _log?.Invoke($"Mic capture failed: {ex.Message}");
        MicLevelChanged?.Invoke(0);
        MicFailed?.Invoke(ex);
    }

    private void OnLoopbackFailed(Exception ex)
    {
        _log?.Invoke($"Desktop audio capture failed: {ex.Message}");
    }
}
