// SPDX-FileCopyrightText: 2026 FocusFlow contributors
// SPDX-License-Identifier: AGPL-3.0-only

part of '../../notification_adapter.dart';

/// Test fake for notification adapter behavior.
final class FakeNotificationAdapter implements NotificationAdapter {
  /// Private state stored as `_feedbackController` for this implementation.
  /// The field is intentionally scoped to this library so only the owning type can change the related scheduler or UI behavior.
  final StreamController<NotificationFeedback> _feedbackController =
      StreamController<NotificationFeedback>.broadcast();

  /// Private state stored as `_scheduled` for this implementation.
  /// The field is intentionally scoped to this library so only the owning type can change the related scheduler or UI behavior.
  final Map<String, NotificationRequest> _scheduled =
      <String, NotificationRequest>{};

  /// Private state stored as `_cancelledIds` for this implementation.
  /// The field is intentionally scoped to this library so only the owning type can change the related scheduler or UI behavior.
  final List<String> _cancelledIds = <String>[];

  /// Snapshot of currently scheduled requests by id.
  Map<String, NotificationRequest> get scheduledRequests =>
      Map<String, NotificationRequest>.unmodifiable(_scheduled);

  /// Snapshot of cancellation ids in call order.
  List<String> get cancelledIds => List<String>.unmodifiable(_cancelledIds);

  /// Returns the derived `feedback` value for this object.
  /// Keeping this as an accessor gives callers a stable read surface while the owning type controls how the value is calculated.
  @override
  Stream<NotificationFeedback> get feedback => _feedbackController.stream;

  /// Runs the `schedule` operation and completes after its asynchronous work finishes.
  /// Callers should await the returned future so repository writes, UI refreshes, and failure handling happen in a predictable order.
  @override
  Future<void> schedule(NotificationRequest request) async {
    logger.debug(() => 'Fake notification scheduled. '
        'notificationId=${request.id} '
        'scheduledDateTimeUtc=${request.scheduledDateTimeUtc.toIso8601String()}');
    _scheduled[request.id] = request;
  }

  /// Runs the `cancel` operation and completes after its asynchronous work finishes.
  /// Callers should await the returned future so repository writes, UI refreshes, and failure handling happen in a predictable order.
  @override
  Future<void> cancel(String id) async {
    final trimmed = _requiredTrimmed(id, 'id');
    final removed = _scheduled.remove(trimmed) != null;
    _cancelledIds.add(trimmed);
    logger.debug(() => 'Fake notification cancelled. '
        'notificationId=$trimmed removed=$removed');
  }

  /// Emit [event] to feedback listeners.
  void emitFeedback(NotificationFeedback event) {
    logger.debug(() => 'Fake notification feedback emitted. '
        'notificationId=${event.id} type=${event.type.name} '
        'hasAction=${event.actionId != null}');
    _feedbackController.add(event);
  }

  /// Release stream resources held by this fake.
  Future<void> close() {
    logger.debug(() => 'Fake notification adapter closing. '
        'scheduledCount=${_scheduled.length} cancelledCount=${_cancelledIds.length}');
    return _feedbackController.close();
  }
}

/// Top-level helper that performs the `_requiredTrimmed` operation for this file.
/// Keeping the helper private makes the surrounding flow easier to read while preventing unrelated libraries from depending on implementation detail.
String _requiredTrimmed(String value, String name) {
  final trimmed = value.trim();
  if (trimmed.isEmpty) {
    throw ArgumentError.value(value, name, 'Value must not be blank.');
  }
  return trimmed;
}
