// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';
import 'dart:typed_data';

/// Merges the values in a stream that are sent less than [duration] apart.
///
/// To minimize latency, the merged stream will always emit the first value that
/// is sent after a pause of at least [duration] long. After the first message,
/// all values that are sent within [duration] will be merged into one.
Stream<Uint8List> debounceDataStream(Stream<Uint8List> stream, [Duration duration = const Duration(milliseconds: 100)]) {
  final StreamController<Uint8List> controller = StreamController<Uint8List>();
  final BytesBuilder buffer = BytesBuilder(copy: false);

  bool isDone = false;
  Timer? timer;

  // Called when timer triggers, sends out the buffered messages.
  void onTimer() {
    if (buffer.isNotEmpty) {
      controller.add(buffer.toBytes());
      buffer.clear();
      if (isDone) {
        controller.close();
      } else {
        // Start another timer even if we have nothing to send right now, so
        // that outgoing messages are at least [duration] apart.
        timer = Timer(duration, onTimer);
      }
    } else {
      timer = null;
    }
  }

  controller.onListen = () {
    final StreamSubscription<Uint8List> subscription = stream.listen((Uint8List data) {
      if (timer == null) {
        controller.add(data);
        // Start the timer to make sure that the next message is at least [duration] apart.
        timer = Timer(duration, onTimer);
      } else {
        buffer.add(data);
      }
    }, onError: (Object error, StackTrace stackTrace) {
      // Forward the error.
      controller.addError(error, stackTrace);
    }, onDone: () {
      isDone = true;
      // Delay closing the channel if we still have buffered data.
      if (timer == null) {
        controller.close();
      }
    });

    controller.onCancel = () {
      subscription.cancel();
    };
  };

  return controller.stream;
}
