import 'dart:typed_data';
import 'package:audio_session/audio_session.dart';
import 'package:flutter_sound/flutter_sound.dart';

/// Wraps [FlutterSoundPlayer] for streaming raw PCM 16kHz mono playback
/// with a simple jitter buffer to smooth out network-induced timing variance.
///
/// Uses [AudioSession] to configure playAndRecord mode so playback works
/// alongside mic capture, with hardware echo cancellation on Android.
class PcmPlayer {
  FlutterSoundPlayer? _player;
  bool _initialized = false;

  // Jitter buffer
  final List<Uint8List> _buffer = [];
  int _bufferedBytes = 0;
  bool _playbackStarted = false;

  /// Number of bytes to accumulate before starting playback.
  /// 4800 bytes = 150 ms at 16 kHz, 16-bit mono (2 bytes per sample).
  static const int _bufferThreshold = 4800;

  /// Open the player and start a streaming session.
  ///
  /// Configures the audio session for simultaneous playback + recording,
  /// routes audio to the loudspeaker, and enables voice-chat mode so
  /// Android activates hardware AEC (echo cancellation).
  Future<void> init() async {
    if (_initialized) return;

    // Configure the shared audio session for voice call mode.
    // - playAndRecord: allows mic capture and speaker output simultaneously
    // - voiceCommunication: enables Android hardware AEC (echo cancellation)
    // - defaultToSpeaker: routes audio to loudspeaker instead of earpiece
    final session = await AudioSession.instance;
    await session.configure(AudioSessionConfiguration(
      avAudioSessionCategory: AVAudioSessionCategory.playAndRecord,
      avAudioSessionCategoryOptions:
          AVAudioSessionCategoryOptions.defaultToSpeaker |
          AVAudioSessionCategoryOptions.allowBluetooth,
      avAudioSessionMode: AVAudioSessionMode.voiceChat,
      androidAudioAttributes: const AndroidAudioAttributes(
        contentType: AndroidAudioContentType.speech,
        usage: AndroidAudioUsage.voiceCommunication,
      ),
      androidAudioFocusGainType: AndroidAudioFocusGainType.gain,
    ));

    _player = FlutterSoundPlayer();
    await _player!.openPlayer();

    await _player!.startPlayerFromStream(
      codec: Codec.pcm16,
      sampleRate: 16000,
      numChannels: 1,
      interleaved: true,
      bufferSize: 8192,
    );

    _initialized = true;
  }

  /// Feed raw PCM 16-bit signed LE mono 16 kHz data for playback.
  ///
  /// Incoming chunks are held in a jitter buffer until [_bufferThreshold] bytes
  /// have accumulated.  Once playback has started, new chunks are forwarded to
  /// the underlying player immediately.
  Future<void> feed(Uint8List pcmData) async {
    if (!_initialized || _player == null) return;

    if (!_playbackStarted) {
      _buffer.add(pcmData);
      _bufferedBytes += pcmData.length;
      if (_bufferedBytes >= _bufferThreshold) {
        _playbackStarted = true;
        await _drainBuffer();
      }
      return;
    }

    // ignore: deprecated_member_use
    await _player!.feedUint8FromStream(pcmData);
  }

  /// Drain all queued chunks into the player in order.
  Future<void> _drainBuffer() async {
    while (_buffer.isNotEmpty) {
      final chunk = _buffer.removeAt(0);
      _bufferedBytes -= chunk.length;
      // ignore: deprecated_member_use
      await _player!.feedUint8FromStream(chunk);
    }
  }

  /// Clear the jitter buffer and reset playback state.
  void reset() {
    _buffer.clear();
    _bufferedBytes = 0;
    _playbackStarted = false;
  }

  /// Toggle speaker mode (earpiece vs loudspeaker).
  ///
  /// Note: With voiceCommunication usage, Android defaults to earpiece.
  /// This method is a placeholder — actual switching requires platform
  /// channel or AudioManager API. The AudioSession config above already
  /// sets defaultToSpeaker.
  Future<void> setSpeakerOn(bool on) async {
    // AudioSession configuration already defaults to speaker.
    // Fine-grained toggling would need a platform channel to call
    // Android AudioManager.setSpeakerphoneOn(on).
  }

  /// Stop playback and release resources.
  Future<void> dispose() async {
    reset();
    if (_player != null) {
      try {
        await _player!.stopPlayer();
      } catch (_) {}
      await _player!.closePlayer();
      _player = null;
    }
    _initialized = false;
  }
}
